diff --git a/Cargo.toml b/Cargo.toml index b4ba58bac9f..2250f06b3ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,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 4ec484e77e1..05302fd88a0 100755 --- a/ci/ci-tests.sh +++ b/ci/ci-tests.sh @@ -166,8 +166,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 036ac4e84ba..a1487721b24 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 @@ -146,7 +148,7 @@ mod sealed { // Byte 2 BasicMPP | Wumbo | AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx, // Byte 3 - RouteBlinding | ShutdownAnySegwit | Taproot, + RouteBlinding | ShutdownAnySegwit | DualFund | Taproot, // Byte 4 OnionMessages, // Byte 5 @@ -167,7 +169,7 @@ mod sealed { // Byte 2 BasicMPP | Wumbo | AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx, // Byte 3 - RouteBlinding | ShutdownAnySegwit | Taproot, + RouteBlinding | ShutdownAnySegwit | DualFund | Taproot, // Byte 4 OnionMessages, // Byte 5 @@ -502,6 +504,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 033086981cb..92991f4b228 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -606,6 +606,20 @@ impl_writeable_tlv_based_enum_upgradable!(PaymentFailureReason, (10, UnexpectedError) => {}, ); +/// Used to indicate the kind of funding for this channel by the channel acceptor (us). +/// +/// Allows the differentiation between a request for a dual-funded and non-dual-funded channel. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum InboundChannelFunds { + /// For a non-dual-funded channel, the `push_msat` value from the channel initiator to us. + PushMsat(u64), + /// Indicates the open request is for a dual funded channel. + /// + /// Note that these channels do not support starting with initial funds pushed from the counterparty, + /// who is the channel opener in this case. + DualFunded, +} + /// An Event which you should probably take some action in response to. /// /// Note that while Writeable and Readable are implemented for Event, you probably shouldn't use @@ -1337,16 +1351,17 @@ pub enum Event { }, /// Indicates a request to open a new channel by a peer. /// - /// To accept the request, call [`ChannelManager::accept_inbound_channel`]. To reject the request, - /// call [`ChannelManager::force_close_without_broadcasting_txn`]. Note that a ['ChannelClosed`] - /// event will _not_ be triggered if the channel is rejected. + /// To accept the request (and in the case of a dual-funded channel, not contribute funds), + /// call [`ChannelManager::accept_inbound_channel`]. + /// To reject the request, call [`ChannelManager::force_close_without_broadcasting_txn`]. + /// Note that a ['ChannelClosed`] event will _not_ be triggered if the channel is rejected. /// /// The event is only triggered when a new open channel request is received and the /// [`UserConfig::manually_accept_inbound_channels`] config flag is set to true. /// /// # Failure Behavior and Persistence /// This event will eventually be replayed after failures-to-handle (i.e., the event handler - /// returning `Err(ReplayEvent ())`) and will be persisted across restarts. + /// returning `Err(ReplayEvent ())`) and won't be persisted across restarts. /// /// [`ChannelManager::accept_inbound_channel`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel /// [`ChannelManager::force_close_without_broadcasting_txn`]: crate::ln::channelmanager::ChannelManager::force_close_without_broadcasting_txn @@ -1373,8 +1388,10 @@ pub enum Event { counterparty_node_id: PublicKey, /// The channel value of the requested channel. funding_satoshis: u64, - /// Our starting balance in the channel if the request is accepted, in milli-satoshi. - push_msat: u64, + /// If `channel_negotiation_type` is `InboundChannelFunds::DualFunded`, this indicates that the peer wishes to + /// open a dual-funded channel. Otherwise, this field will be `InboundChannelFunds::PushMsats`, + /// indicating the `push_msats` value our peer is pushing to us for a non-dual-funded channel. + channel_negotiation_type: InboundChannelFunds, /// The features that this channel will operate with. If you reject the channel, a /// well-behaved counterparty may automatically re-attempt the channel with a new set of /// feature flags. diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 8439e9b6da3..14f2db55ce6 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -9,11 +9,13 @@ use bitcoin::amount::Amount; use bitcoin::constants::ChainHash; -use bitcoin::script::{Script, ScriptBuf, Builder}; -use bitcoin::transaction::Transaction; +use bitcoin::script::{Script, ScriptBuf, Builder, WScriptHash}; +use bitcoin::transaction::{Transaction, TxIn}; use bitcoin::sighash; use bitcoin::sighash::EcdsaSighashType; use bitcoin::consensus::encode; +use bitcoin::absolute::LockTime; +use bitcoin::Weight; use bitcoin::hashes::Hash; use bitcoin::hashes::sha256::Hash as Sha256; @@ -28,6 +30,11 @@ use bitcoin::secp256k1; use crate::ln::types::ChannelId; use crate::types::payment::{PaymentPreimage, PaymentHash}; use crate::types::features::{ChannelTypeFeatures, InitFeatures}; +use crate::ln::interactivetxs::{ + get_output_weight, HandleTxCompleteValue, 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}; @@ -44,14 +51,14 @@ use crate::ln::chan_utils::{ use crate::ln::chan_utils; use crate::ln::onion_utils::HTLCFailReason; use crate::chain::BestBlock; -use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, LowerBoundedFeeEstimator}; +use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, LowerBoundedFeeEstimator, fee_for_weight}; use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, ChannelMonitorUpdateStep, LATENCY_GRACE_PERIOD_BLOCKS}; use crate::chain::transaction::{OutPoint, TransactionData}; use crate::sign::ecdsa::EcdsaChannelSigner; use crate::sign::{EntropySource, ChannelSigner, SignerProvider, NodeSigner, Recipient}; -use crate::events::ClosureReason; +use crate::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}; @@ -895,6 +902,7 @@ pub(super) struct MonitorRestoreUpdates { pub funding_broadcastable: Option, pub channel_ready: Option, pub announcement_sigs: Option, + pub tx_signatures: Option, } /// The return value of `signer_maybe_unblocked` @@ -1121,9 +1129,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), } @@ -1137,9 +1144,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, } } @@ -1149,9 +1154,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, } } @@ -1252,6 +1255,7 @@ pub(super) struct ChannelContext where SP::Target: SignerProvider { monitor_pending_failures: Vec<(HTLCSource, PaymentHash, HTLCFailReason)>, monitor_pending_finalized_fulfills: Vec, monitor_pending_update_adds: Vec, + monitor_pending_tx_signatures: Option, /// If we went to send a revoke_and_ack but our signer was unable to give us a signature, /// we should retry at some point in the future when the signer indicates it may have a @@ -1494,6 +1498,21 @@ 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, + // The `next_funding_txid` field allows peers to finalize the signing steps of an interactive + // transaction construction, or safely abort that transaction if it was not signed by one of the + // peers, who has thus already removed it from its state. + // + // If we've sent `commtiment_signed` for an interactively constructed transaction + // during a signing session, 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. + // + // See the spec for further details on this: + // * `channel_reestablish`-sending node: https://github.com/lightning/bolts/blob/247e83d/02-peer-protocol.md?plain=1#L2466-L2470 + // * `channel_reestablish`-receiving node: https://github.com/lightning/bolts/blob/247e83d/02-peer-protocol.md?plain=1#L2520-L2531 + // + // TODO(dual_funding): Persist this when we actually contribute funding inputs. For now we always + // send an empty witnesses array in `tx_signatures` as a V2 channel acceptor + next_funding_txid: Option, } /// A channel struct implementing this trait can receive an initial counterparty commitment @@ -1642,6 +1661,211 @@ impl InitialRemoteCommitmentReceiver for InboundV1Channel whe } } +impl InitialRemoteCommitmentReceiver for Channel where SP::Target: SignerProvider { + fn context(&self) -> &ChannelContext { + &self.context + } + + fn context_mut(&mut self) -> &mut ChannelContext { + &mut self.context + } + + fn received_msg(&self) -> &'static str { + "commitment_signed" + } +} + +pub(super) trait InteractivelyFunded where SP::Target: SignerProvider { + fn context(&self) -> &ChannelContext; + + fn context_mut(&mut self) -> &mut ChannelContext; + + fn interactive_tx_constructor_mut(&mut self) -> &mut Option; + + fn dual_funding_context(&self) -> &DualFundingChannelContext; + + fn tx_add_input(&mut self, msg: &msgs::TxAddInput) -> InteractiveTxMessageSendResult { + InteractiveTxMessageSendResult(match self.interactive_tx_constructor_mut() { + Some(ref mut tx_constructor) => tx_constructor.handle_tx_add_input(msg).map_err( + |reason| reason.into_tx_abort_msg(self.context().channel_id())), + None => Err(msgs::TxAbort { + channel_id: self.context().channel_id(), + data: b"No interactive transaction negotiation in progress".to_vec() + }), + }) + } + + fn tx_add_output(&mut self, msg: &msgs::TxAddOutput)-> InteractiveTxMessageSendResult { + InteractiveTxMessageSendResult(match self.interactive_tx_constructor_mut() { + Some(ref mut tx_constructor) => tx_constructor.handle_tx_add_output(msg).map_err( + |reason| reason.into_tx_abort_msg(self.context().channel_id())), + None => Err(msgs::TxAbort { + channel_id: self.context().channel_id(), + data: b"No interactive transaction negotiation in progress".to_vec() + }), + }) + } + + fn tx_remove_input(&mut self, msg: &msgs::TxRemoveInput)-> InteractiveTxMessageSendResult { + InteractiveTxMessageSendResult(match self.interactive_tx_constructor_mut() { + Some(ref mut tx_constructor) => tx_constructor.handle_tx_remove_input(msg).map_err( + |reason| reason.into_tx_abort_msg(self.context().channel_id())), + None => Err(msgs::TxAbort { + channel_id: self.context().channel_id(), + data: b"No interactive transaction negotiation in progress".to_vec() + }), + }) + } + + fn tx_remove_output(&mut self, msg: &msgs::TxRemoveOutput)-> InteractiveTxMessageSendResult { + InteractiveTxMessageSendResult(match self.interactive_tx_constructor_mut() { + Some(ref mut tx_constructor) => tx_constructor.handle_tx_remove_output(msg).map_err( + |reason| reason.into_tx_abort_msg(self.context().channel_id())), + None => Err(msgs::TxAbort { + channel_id: self.context().channel_id(), + data: b"No interactive transaction negotiation in progress".to_vec() + }), + }) + } + + fn tx_complete(&mut self, msg: &msgs::TxComplete) -> HandleTxCompleteResult { + let tx_constructor = match self.interactive_tx_constructor_mut() { + Some(ref mut tx_constructor) => tx_constructor, + None => { + let tx_abort = msgs::TxAbort { + channel_id: msg.channel_id, + data: b"No interactive transaction negotiation in progress".to_vec(), + }; + return HandleTxCompleteResult(Err(tx_abort)); + }, + }; + + let tx_complete = match tx_constructor.handle_tx_complete(msg) { + Ok(tx_complete) => tx_complete, + Err(reason) => { + return HandleTxCompleteResult(Err(reason.into_tx_abort_msg(msg.channel_id))) + } + }; + + if let HandleTxCompleteValue::SendTxComplete(_, ref signing_session) = tx_complete { + self.context_mut().next_funding_txid = Some(signing_session.unsigned_tx.compute_txid()); + }; + + HandleTxCompleteResult(Ok(tx_complete)) + } + + fn funding_tx_constructed( + &mut self, signing_session: &mut InteractiveTxSigningSession, logger: &L + ) -> Result<(msgs::CommitmentSigned, Option), ChannelError> + where + L::Target: Logger + { + let our_funding_satoshis = self.dual_funding_context().our_funding_satoshis; + let context = self.context_mut(); + + let mut output_index = None; + let expected_spk = context.get_funding_redeemscript().to_p2wsh(); + for (idx, outp) in signing_session.unsigned_tx.outputs().enumerate() { + if outp.script_pubkey() == &expected_spk && outp.value() == context.get_value_satoshis() { + if output_index.is_some() { + return Err(ChannelError::Close( + ( + "Multiple outputs matched the expected script and value".to_owned(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + } + output_index = Some(idx as u16); + } + } + let outpoint = if let Some(output_index) = output_index { + OutPoint { txid: signing_session.unsigned_tx.compute_txid(), index: output_index } + } else { + return Err(ChannelError::Close( + ( + "No output matched the funding script_pubkey".to_owned(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + }; + context.channel_transaction_parameters.funding_outpoint = Some(outpoint); + context.holder_signer.as_mut().provide_channel_parameters(&context.channel_transaction_parameters); + + let commitment_signed = context.get_initial_commitment_signed(logger); + let commitment_signed = match commitment_signed { + Ok(commitment_signed) => { + context.funding_transaction = Some(signing_session.unsigned_tx.build_unsigned_tx()); + commitment_signed + }, + Err(err) => { + context.channel_transaction_parameters.funding_outpoint = None; + return Err(ChannelError::Close((err.to_string(), ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }))) + }, + }; + + let funding_ready_for_sig_event = None; + if signing_session.local_inputs_count() == 0 { + debug_assert_eq!(our_funding_satoshis, 0); + if signing_session.provide_holder_witnesses(context.channel_id, Vec::new()).is_err() { + debug_assert!( + false, + "Zero inputs were provided & zero witnesses were provided, but a count mismatch was somehow found", + ); + } + } else { + // TODO(dual_funding): Send event for signing if we've contributed funds. + // Inform the user that SIGHASH_ALL must be used for all signatures when contributing + // inputs/signatures. + // Also warn the user that we don't do anything to prevent the counterparty from + // providing non-standard witnesses which will prevent the funding transaction from + // confirming. This warning must appear in doc comments wherever the user is contributing + // funds, whether they are initiator or acceptor. + // + // The following warning can be used when the APIs allowing contributing inputs become available: + //
+ // WARNING: LDK makes no attempt to prevent the counterparty from using non-standard inputs which + // will prevent the funding transaction from being relayed on the bitcoin network and hence being + // confirmed. + //
+ } + + context.channel_state = ChannelState::FundingNegotiated; + + // Clear the interactive transaction constructor + self.interactive_tx_constructor_mut().take(); + + Ok((commitment_signed, funding_ready_for_sig_event)) + } +} + +impl InteractivelyFunded for OutboundV2Channel where SP::Target: SignerProvider { + fn context(&self) -> &ChannelContext { + &self.context + } + fn context_mut(&mut self) -> &mut ChannelContext { + &mut self.context + } + fn dual_funding_context(&self) -> &DualFundingChannelContext { + &self.dual_funding_context + } + fn interactive_tx_constructor_mut(&mut self) -> &mut Option { + &mut self.interactive_tx_constructor + } +} + +impl InteractivelyFunded for InboundV2Channel where SP::Target: SignerProvider { + fn context(&self) -> &ChannelContext { + &self.context + } + fn context_mut(&mut self) -> &mut ChannelContext { + &mut self.context + } + fn dual_funding_context(&self) -> &DualFundingChannelContext { + &self.dual_funding_context + } + fn interactive_tx_constructor_mut(&mut self) -> &mut Option { + &mut self.interactive_tx_constructor + } +} + impl ChannelContext where SP::Target: SignerProvider { fn new_for_inbound_channel<'a, ES: Deref, F: Deref, L: Deref>( fee_estimator: &'a LowerBoundedFeeEstimator, @@ -1887,6 +2111,7 @@ impl ChannelContext where SP::Target: SignerProvider { monitor_pending_failures: Vec::new(), monitor_pending_finalized_fulfills: Vec::new(), monitor_pending_update_adds: Vec::new(), + monitor_pending_tx_signatures: None, signer_pending_revoke_and_ack: false, signer_pending_commitment_update: false, @@ -1980,6 +2205,8 @@ impl ChannelContext where SP::Target: SignerProvider { blocked_monitor_updates: Vec::new(), is_manual_broadcast: false, + + next_funding_txid: None, }; Ok(channel_context) @@ -2121,6 +2348,7 @@ impl ChannelContext where SP::Target: SignerProvider { monitor_pending_failures: Vec::new(), monitor_pending_finalized_fulfills: Vec::new(), monitor_pending_update_adds: Vec::new(), + monitor_pending_tx_signatures: None, signer_pending_revoke_and_ack: false, signer_pending_commitment_update: false, @@ -2211,6 +2439,7 @@ impl ChannelContext where SP::Target: SignerProvider { blocked_monitor_updates: Vec::new(), local_initiated_shutdown: None, is_manual_broadcast: false, + next_funding_txid: None, }) } @@ -2984,7 +3213,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()) @@ -3771,6 +4000,91 @@ impl ChannelContext where SP::Target: SignerProvider { self.channel_transaction_parameters.channel_type_features = self.channel_type.clone(); Ok(()) } + + /// Asserts that the commitment tx numbers have not advanced from their initial number. + fn assert_no_commitment_advancement(&self, msg_name: &str) { + if self.commitment_secrets.get_min_seen_secret() != (1 << 48) || + self.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || + self.holder_commitment_point.transaction_number() != INITIAL_COMMITMENT_NUMBER { + debug_assert!(false, "Should not have advanced channel commitment tx numbers prior to {}", + msg_name); + } + } + + fn get_initial_counterparty_commitment_signature( + &self, logger: &L + ) -> Result + where + SP::Target: SignerProvider, + L::Target: Logger + { + let counterparty_keys = self.build_remote_transaction_keys(); + let counterparty_initial_commitment_tx = self.build_commitment_transaction( + self.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx; + match self.holder_signer { + // TODO (taproot|arik): move match into calling method for Taproot + ChannelSignerType::Ecdsa(ref ecdsa) => { + ecdsa.sign_counterparty_commitment(&counterparty_initial_commitment_tx, Vec::new(), Vec::new(), &self.secp_ctx) + .map(|(signature, _)| signature) + .map_err(|_| ChannelError::Close( + ( + "Failed to get signatures for new commitment_signed".to_owned(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))) + }, + // TODO (taproot|arik) + #[cfg(taproot)] + _ => todo!(), + } + } + + fn get_initial_commitment_signed( + &mut self, logger: &L + ) -> Result + where + SP::Target: SignerProvider, + L::Target: Logger + { + if !matches!( + self.channel_state, ChannelState::NegotiatingFunding(flags) + if flags == (NegotiatingFundingFlags::OUR_INIT_SENT | NegotiatingFundingFlags::THEIR_INIT_SENT)) { + panic!("Tried to get an initial commitment_signed messsage at a time other than immediately after initial handshake completion (or tried to get funding_created twice)"); + } + self.assert_no_commitment_advancement("initial commitment_signed"); + + let signature = match self.get_initial_counterparty_commitment_signature(logger) { + Ok(res) => res, + Err(e) => { + log_error!(logger, "Got bad signatures: {:?}!", e); + return Err(e); + } + }; + + log_info!(logger, "Generated commitment_signed for peer for channel {}", &self.channel_id()); + + Ok(msgs::CommitmentSigned { + channel_id: self.channel_id, + htlc_signatures: vec![], + signature, + batch: None, + #[cfg(taproot)] + partial_signature_with_nonce: None, + }) + } + + #[cfg(test)] + pub fn get_initial_counterparty_commitment_signature_for_test( + &mut self, logger: &L, channel_transaction_parameters: ChannelTransactionParameters, + counterparty_cur_commitment_point_override: PublicKey, + ) -> Result + where + SP::Target: SignerProvider, + L::Target: Logger + { + self.counterparty_cur_commitment_point = Some(counterparty_cur_commitment_point_override); + self.channel_transaction_parameters = channel_transaction_parameters; + self.get_initial_counterparty_commitment_signature(logger) + } } // Internal utility functions for channels @@ -3822,33 +4136,76 @@ 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)], + total_witness_weight: Weight, funding_feerate_sat_per_1000_weight: u32, + holder_dust_limit_satoshis: u64, +) -> Result { + let mut total_input_satoshis = 0u64; + let mut our_contributed_weight = 0u64; + + for (idx, input) in funding_inputs.iter().enumerate() { + if let Some(output) = input.1.as_transaction().output.get(input.0.previous_output.vout as usize) { + total_input_satoshis = total_input_satoshis.saturating_add(output.value.to_sat()); + } else { + return Err(APIError::APIMisuseError { + err: format!("Transaction with txid {} does not have an output with vout of {} corresponding to TxIn at funding_inputs[{}]", + input.1.as_transaction().compute_txid(), input.0.previous_output.vout, idx) }); + } + } + our_contributed_weight = our_contributed_weight.saturating_add(total_witness_weight.to_wu()); + + // If we are the initiator, we must pay for weight of all common fields in the funding transaction. + if is_initiator { + our_contributed_weight = our_contributed_weight + .saturating_add(TX_COMMON_FIELDS_WEIGHT) + // The weight of a P2WSH output to be added later. + // + // NOTE: The witness script hash given here is irrelevant as it's a fixed size and we just want + // to calculate the contributed weight, so we use an all-zero hash. + .saturating_add(get_output_weight(&ScriptBuf::new_p2wsh( + &WScriptHash::from_raw_hash(Hash::all_zeros()) + )).to_wu()) + } + + let funding_satoshis = total_input_satoshis + .saturating_sub(fee_for_weight(funding_feerate_sat_per_1000_weight, our_contributed_weight)); + if funding_satoshis < holder_dust_limit_satoshis { + Ok(0) + } else { + Ok(funding_satoshis) + } +} + /// Context for dual-funded channels. -#[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, - /// The amount in satoshis our counterparty will be contributing to the channel. - pub their_funding_satoshis: u64, /// The funding transaction locktime suggested by the initiator. If set by us, it is always set /// to the current block height to align incentives against fee-sniping. - pub funding_tx_locktime: 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. + #[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled. + 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))] - pub dual_funding_channel_context: Option, + pub interactive_tx_signing_session: Option, } #[cfg(any(test, fuzzing))] @@ -4628,6 +4985,41 @@ impl Channel where Ok(()) } + pub fn commitment_signed_initial_v2( + &mut self, msg: &msgs::CommitmentSigned, best_block: BestBlock, signer_provider: &SP, logger: &L + ) -> Result::EcdsaSigner>, ChannelError> + where L::Target: Logger + { + if !matches!(self.context.channel_state, ChannelState::FundingNegotiated) { + return Err(ChannelError::Close( + ( + "Received initial commitment_signed before funding transaction constructed!".to_owned(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + } + self.context.assert_no_commitment_advancement("initial commitment_signed"); + + let (channel_monitor, _) = self.initial_commitment_signed( + self.context.channel_id(), msg.signature, + self.context.cur_counterparty_commitment_transaction_number, best_block, signer_provider, logger)?; + + log_info!(logger, "Received initial commitment_signed from peer for channel {}", &self.context.channel_id()); + + let need_channel_ready = self.check_get_channel_ready(0, logger).is_some(); + self.context.channel_state = ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::new()); + self.monitor_updating_paused(false, false, need_channel_ready, Vec::new(), Vec::new(), Vec::new()); + + if let Some(tx_signatures) = self.interactive_tx_signing_session.as_mut().and_then( + |session| session.received_commitment_signed() + ) { + // We're up first for submitting our tx_signatures, but our monitor has not persisted yet + // so they'll be sent as soon as that's done. + self.context.monitor_pending_tx_signatures = Some(tx_signatures); + } + + Ok(channel_monitor) + } + pub fn commitment_signed(&mut self, msg: &msgs::CommitmentSigned, logger: &L) -> Result, ChannelError> where L::Target: Logger { @@ -5298,6 +5690,70 @@ impl Channel where } } + pub fn tx_signatures(&mut self, msg: &msgs::TxSignatures, logger: &L) -> Result<(Option, Option), ChannelError> + where L::Target: Logger + { + if !matches!(self.context.channel_state, ChannelState::FundingNegotiated) { + return Err(ChannelError::close("Received tx_signatures in strange state!".to_owned())); + } + + if let Some(ref mut signing_session) = self.interactive_tx_signing_session { + if msg.witnesses.len() != signing_session.remote_inputs_count() { + return Err(ChannelError::Warn( + "Witness count did not match contributed input count".to_string() + )); + } + + for witness in &msg.witnesses { + if witness.is_empty() { + return Err(ChannelError::Close( + ( + "Unexpected empty witness in tx_signatures received".to_string(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + } + + // TODO(dual_funding): Check all sigs are SIGHASH_ALL. + + // TODO(dual_funding): I don't see how we're going to be able to ensure witness-standardness + // for spending. Doesn't seem to be anything in rust-bitcoin. + } + + if msg.tx_hash != signing_session.unsigned_tx.compute_txid() { + return Err(ChannelError::Close( + ( + "The txid for the transaction does not match".to_string(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + } + + let (tx_signatures_opt, funding_tx_opt) = signing_session.received_tx_signatures(msg.clone()) + .map_err(|_| ChannelError::Warn("Witness count did not match contributed input count".to_string()))?; + if funding_tx_opt.is_some() { + self.context.channel_state = ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::new()); + } + self.context.funding_transaction = funding_tx_opt.clone(); + + self.context.next_funding_txid = None; + + // Clear out the signing session + self.interactive_tx_signing_session = None; + + if tx_signatures_opt.is_some() && self.context.channel_state.is_monitor_update_in_progress() { + log_debug!(logger, "Not sending tx_signatures: a monitor update is in progress. Setting monitor_pending_tx_signatures."); + self.context.monitor_pending_tx_signatures = tx_signatures_opt; + return Ok((None, None)); + } + + Ok((tx_signatures_opt, funding_tx_opt)) + } else { + Err(ChannelError::Close(( + "Unexpected tx_signatures. No funding transaction awaiting signatures".to_string(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))) + } + } + /// Queues up an outbound update fee by placing it in the holding cell. You should call /// [`Self::maybe_free_holding_cell_htlcs`] in order to actually generate and send the /// commitment update. @@ -5531,6 +5987,10 @@ impl Channel where mem::swap(&mut finalized_claimed_htlcs, &mut self.context.monitor_pending_finalized_fulfills); let mut pending_update_adds = Vec::new(); mem::swap(&mut pending_update_adds, &mut self.context.monitor_pending_update_adds); + // For channels established with V2 establishment we won't send a `tx_signatures` when we're in + // MonitorUpdateInProgress (and we assume the user will never directly broadcast the funding + // transaction and waits for us to do it). + let tx_signatures = self.context.monitor_pending_tx_signatures.take(); if self.context.channel_state.is_peer_disconnected() { self.context.monitor_pending_revoke_and_ack = false; @@ -5538,7 +5998,7 @@ impl Channel where return MonitorRestoreUpdates { raa: None, commitment_update: None, order: RAACommitmentOrder::RevokeAndACKFirst, accepted_htlcs, failed_htlcs, finalized_claimed_htlcs, pending_update_adds, - funding_broadcastable, channel_ready, announcement_sigs + funding_broadcastable, channel_ready, announcement_sigs, tx_signatures }; } @@ -5572,7 +6032,7 @@ impl Channel where match order { RAACommitmentOrder::CommitmentFirst => "commitment", RAACommitmentOrder::RevokeAndACKFirst => "RAA"}); MonitorRestoreUpdates { raa, commitment_update, order, accepted_htlcs, failed_htlcs, finalized_claimed_htlcs, - pending_update_adds, funding_broadcastable, channel_ready, announcement_sigs + pending_update_adds, funding_broadcastable, channel_ready, announcement_sigs, tx_signatures } } @@ -7343,10 +7803,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, } } @@ -7885,11 +8342,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { ) { panic!("Tried to get a funding_created messsage at a time other than immediately after initial handshake completion (or tried to get funding_created twice)"); } - if self.context.commitment_secrets.get_min_seen_secret() != (1 << 48) || - self.context.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || - self.context.holder_commitment_point.transaction_number() != INITIAL_COMMITMENT_NUMBER { - panic!("Should not have advanced channel commitment tx numbers prior to funding_created"); - } + self.context.assert_no_commitment_advancement("funding_created"); self.context.channel_transaction_parameters.funding_outpoint = Some(funding_txo); self.context.holder_signer.as_mut().provide_channel_parameters(&self.context.channel_transaction_parameters); @@ -7996,7 +8449,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. @@ -8013,11 +8467,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { if !matches!(self.context.channel_state, ChannelState::FundingNegotiated) { return Err((self, ChannelError::close("Received funding_signed in strange state!".to_owned()))); } - if self.context.commitment_secrets.get_min_seen_secret() != (1 << 48) || - self.context.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || - self.context.holder_commitment_point.transaction_number() != INITIAL_COMMITMENT_NUMBER { - panic!("Should not have advanced channel commitment tx numbers prior to funding_created"); - } + self.context.assert_no_commitment_advancement("funding_created"); let (channel_monitor, _) = match self.initial_commitment_signed( self.context.channel_id(), @@ -8032,8 +8482,7 @@ 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_signing_session: None, }; let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some(); @@ -8142,7 +8591,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) } @@ -8151,7 +8600,7 @@ impl InboundV1Channel where SP::Target: SignerProvider { /// should be sent back to the counterparty node. /// /// [`msgs::AcceptChannel`]: crate::ln::msgs::AcceptChannel - pub fn accept_inbound_channel(&mut self) -> msgs::AcceptChannel { + pub fn accept_inbound_channel(&self) -> msgs::AcceptChannel { if self.context.is_outbound() { panic!("Tried to send accept_channel for an outbound channel?"); } @@ -8232,11 +8681,7 @@ impl InboundV1Channel where SP::Target: SignerProvider { // channel. return Err((self, ChannelError::close("Received funding_created after we got the channel!".to_owned()))); } - if self.context.commitment_secrets.get_min_seen_secret() != (1 << 48) || - self.context.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || - self.context.holder_commitment_point.transaction_number() != INITIAL_COMMITMENT_NUMBER { - panic!("Should not have advanced channel commitment tx numbers prior to funding_created"); - } + self.context.assert_no_commitment_advancement("funding_created"); let funding_txo = OutPoint { txid: msg.funding_txid, index: msg.funding_output_index }; self.context.channel_transaction_parameters.funding_outpoint = Some(funding_txo); @@ -8262,8 +8707,7 @@ 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_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()); @@ -8273,21 +8717,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, @@ -8303,7 +8748,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( @@ -8328,10 +8777,11 @@ impl OutboundV2Channel where SP::Target: SignerProvider { unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 }, dual_funding_context: DualFundingChannelContext { our_funding_satoshis: funding_satoshis, - their_funding_satoshis: 0, funding_tx_locktime, funding_feerate_sat_per_1000_weight, - } + our_funding_inputs: funding_inputs, + }, + interactive_tx_constructor: None, }; Ok(chan) } @@ -8394,34 +8844,52 @@ 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 into_channel(self, signing_session: InteractiveTxSigningSession) -> Result, ChannelError>{ + let channel = Channel { + context: self.context, + interactive_tx_signing_session: Some(signing_session), + }; + + Ok(channel) + } } // A not-yet-funded inbound (from counterparty) channel using V2 channel establishment. -#[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)>, total_witness_weight: Weight, + user_id: u128, config: &UserConfig, current_chain_height: u32, logger: &L, ) -> Result, ChannelError> where ES::Target: EntropySource, F::Target: FeeEstimator, L::Target: Logger, { + let funding_satoshis = calculate_our_funding_satoshis( + false, &funding_inputs, total_witness_weight, msg.funding_feerate_sat_per_1000_weight, + msg.common_fields.dust_limit_satoshis + ).map_err(|_| ChannelError::Close( + ( + "Failed to accept channel".to_string(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + )))?; let channel_value_satoshis = funding_satoshis.saturating_add(msg.common_fields.funding_satoshis); let counterparty_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis( channel_value_satoshis, msg.common_fields.dust_limit_satoshis); @@ -8470,25 +8938,44 @@ impl InboundV2Channel where SP::Target: SignerProvider { &context.get_counterparty_pubkeys().revocation_basepoint); context.channel_id = channel_id; - let chan = Self { - context, - unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 }, - dual_funding_context: DualFundingChannelContext { - our_funding_satoshis: funding_satoshis, - their_funding_satoshis: msg.common_fields.funding_satoshis, - funding_tx_locktime: msg.locktime, - funding_feerate_sat_per_1000_weight: msg.funding_feerate_sat_per_1000_weight, - } + let dual_funding_context = DualFundingChannelContext { + our_funding_satoshis: funding_satoshis, + funding_tx_locktime: LockTime::from_consensus(msg.locktime), + funding_feerate_sat_per_1000_weight: msg.funding_feerate_sat_per_1000_weight, + our_funding_inputs: funding_inputs.clone(), }; - Ok(chan) + let interactive_tx_constructor = Some(InteractiveTxConstructor::new( + InteractiveTxConstructorArgs { + entropy_source, + holder_node_id, + counterparty_node_id, + channel_id: context.channel_id, + feerate_sat_per_kw: dual_funding_context.funding_feerate_sat_per_1000_weight, + funding_tx_locktime: dual_funding_context.funding_tx_locktime, + is_initiator: false, + inputs_to_contribute: funding_inputs, + outputs_to_contribute: Vec::new(), + expected_remote_shared_funding_output: Some((context.get_funding_redeemscript().to_p2wsh(), context.channel_value_satoshis)), + } + ).map_err(|_| ChannelError::Close(( + "V2 channel rejected due to sender error".into(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) } + )))?); + + Ok(Self { + context, + dual_funding_context, + interactive_tx_constructor, + unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 }, + }) } /// Marks an inbound channel as accepted and generates a [`msgs::AcceptChannelV2`] message which /// should be sent back to the counterparty node. /// /// [`msgs::AcceptChannelV2`]: crate::ln::msgs::AcceptChannelV2 - pub fn accept_inbound_dual_funded_channel(&mut self) -> msgs::AcceptChannelV2 { + pub fn accept_inbound_dual_funded_channel(&self) -> msgs::AcceptChannelV2 { if self.context.is_outbound() { debug_assert!(false, "Tried to send accept_channel for an outbound channel?"); } @@ -8505,9 +8992,9 @@ impl InboundV2Channel where SP::Target: SignerProvider { self.generate_accept_channel_v2_message() } - /// This function is used to explicitly generate a [`msgs::AcceptChannel`] message for an - /// inbound channel. If the intention is to accept an inbound channel, use - /// [`InboundV1Channel::accept_inbound_channel`] instead. + /// This function is used to explicitly generate a [`msgs::AcceptChannelV2`] message for an + /// inbound dual-funded channel. If the intention is to accept a V1 established inbound channel, + /// use [`InboundV1Channel::accept_inbound_channel`] instead. /// /// [`msgs::AcceptChannelV2`]: crate::ln::msgs::AcceptChannelV2 fn generate_accept_channel_v2_message(&self) -> msgs::AcceptChannelV2 { @@ -8551,9 +9038,19 @@ 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 into_channel(self, signing_session: InteractiveTxSigningSession) -> Result, ChannelError>{ + let channel = Channel { + context: self.context, + interactive_tx_signing_session: Some(signing_session), + }; + + Ok(channel) + } } // Unfunded channel utilities @@ -9009,7 +9506,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.1.0 }); Ok(()) @@ -9299,6 +9797,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch let mut channel_pending_event_emitted = None; let mut channel_ready_event_emitted = None; let mut funding_tx_broadcast_safe_event_emitted = None; + let mut next_funding_txid = funding_transaction.as_ref().map(|tx| tx.compute_txid()); let mut user_id_high_opt: Option = None; let mut channel_keys_id: Option<[u8; 32]> = None; @@ -9359,6 +9858,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch (49, local_initiated_shutdown, option), (51, is_manual_broadcast, option), (53, funding_tx_broadcast_safe_event_emitted, option), + (55, next_funding_txid, option) // Added in 0.0.125 }); let (channel_keys_id, holder_signer) = if let Some(channel_keys_id) = channel_keys_id { @@ -9532,6 +10032,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch monitor_pending_failures, monitor_pending_finalized_fulfills: monitor_pending_finalized_fulfills.unwrap(), monitor_pending_update_adds: monitor_pending_update_adds.unwrap_or_default(), + monitor_pending_tx_signatures: None, signer_pending_revoke_and_ack: false, signer_pending_commitment_update: false, @@ -9618,9 +10119,12 @@ 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 interactively constructed transaction + // during a signing session, 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, }, - #[cfg(any(dual_funding, splicing))] - dual_funding_channel_context: None, + interactive_tx_signing_session: None, }) } } @@ -9812,7 +10316,7 @@ mod tests { // Make sure A's dust limit is as we expect. let open_channel_msg = node_a_chan.get_open_channel(ChainHash::using_genesis_block(network)); let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[7; 32]).unwrap()); - let mut node_b_chan = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_channel_type_features(&config), &channelmanager::provided_init_features(&config), &open_channel_msg, 7, &config, 0, &&logger, /*is_0conf=*/false).unwrap(); + let node_b_chan = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_channel_type_features(&config), &channelmanager::provided_init_features(&config), &open_channel_msg, 7, &config, 0, &&logger, /*is_0conf=*/false).unwrap(); // Node B --> Node A: accept channel, explicitly setting B's dust limit. let mut accept_channel_msg = node_b_chan.accept_inbound_channel(); @@ -9944,7 +10448,7 @@ mod tests { // Create Node B's channel by receiving Node A's open_channel message let open_channel_msg = node_a_chan.get_open_channel(chain_hash); let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[7; 32]).unwrap()); - let mut node_b_chan = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_channel_type_features(&config), &channelmanager::provided_init_features(&config), &open_channel_msg, 7, &config, 0, &&logger, /*is_0conf=*/false).unwrap(); + let node_b_chan = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_channel_type_features(&config), &channelmanager::provided_init_features(&config), &open_channel_msg, 7, &config, 0, &&logger, /*is_0conf=*/false).unwrap(); // Node B --> Node A: accept channel let accept_channel_msg = node_b_chan.accept_inbound_channel(); @@ -10131,7 +10635,7 @@ mod tests { // Make sure A's dust limit is as we expect. let open_channel_msg = node_a_chan.get_open_channel(ChainHash::using_genesis_block(network)); let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[7; 32]).unwrap()); - let mut node_b_chan = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_channel_type_features(&config), &channelmanager::provided_init_features(&config), &open_channel_msg, 7, &config, 0, &&logger, /*is_0conf=*/false).unwrap(); + let node_b_chan = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_channel_type_features(&config), &channelmanager::provided_init_features(&config), &open_channel_msg, 7, &config, 0, &&logger, /*is_0conf=*/false).unwrap(); // Node B --> Node A: accept channel, explicitly setting B's dust limit. let mut accept_channel_msg = node_b_chan.accept_inbound_channel(); @@ -11311,7 +11815,7 @@ mod tests { let open_channel_msg = node_a_chan.get_open_channel(ChainHash::using_genesis_block(network)); let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[7; 32]).unwrap()); - let mut node_b_chan = InboundV1Channel::<&TestKeysInterface>::new( + let node_b_chan = InboundV1Channel::<&TestKeysInterface>::new( &feeest, &&keys_provider, &&keys_provider, diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 1474d99c9ce..86faeac9a68 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -18,7 +18,7 @@ //! imply it needs to fail HTLCs/payments/channels it manages). use bitcoin::block::Header; -use bitcoin::transaction::Transaction; +use bitcoin::transaction::{Transaction, TxIn}; use bitcoin::constants::ChainHash; use bitcoin::key::constants::SECRET_KEY_SIZE; use bitcoin::network::Network; @@ -30,7 +30,7 @@ use bitcoin::hash_types::{BlockHash, Txid}; use bitcoin::secp256k1::{SecretKey,PublicKey}; use bitcoin::secp256k1::Secp256k1; -use bitcoin::{secp256k1, Sequence}; +use bitcoin::{secp256k1, Sequence, Weight}; use crate::events::FundingInfo; use crate::blinded_path::message::{AsyncPaymentsContext, MessageContext, OffersContext}; @@ -42,14 +42,13 @@ use crate::chain::{Confirm, ChannelMonitorUpdateStatus, Watch, BestBlock}; use crate::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator}; use crate::chain::channelmonitor::{Balance, ChannelMonitor, ChannelMonitorUpdate, WithChannelMonitor, ChannelMonitorUpdateStep, HTLC_FAIL_BACK_BUFFER, CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS, ANTI_REORG_DELAY, MonitorEvent}; use crate::chain::transaction::{OutPoint, TransactionData}; -use crate::events; -use crate::events::{Event, EventHandler, EventsProvider, MessageSendEvent, MessageSendEventsProvider, ClosureReason, HTLCDestination, PaymentFailureReason, ReplayEvent}; +use crate::events::{self, Event, EventHandler, EventsProvider, InboundChannelFunds, MessageSendEvent, MessageSendEventsProvider, ClosureReason, HTLCDestination, PaymentFailureReason, ReplayEvent}; // Since this struct is returned in `list_channels` methods, expose it here in case users want to // construct one themselves. use crate::ln::inbound_payment; use crate::ln::types::ChannelId; use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; -use crate::ln::channel::{self, Channel, ChannelPhase, ChannelError, ChannelUpdateStatus, ShutdownResult, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel, WithChannelContext}; +use crate::ln::channel::{self, Channel, ChannelPhase, ChannelError, ChannelUpdateStatus, ShutdownResult, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel, WithChannelContext, InboundV2Channel, InteractivelyFunded as _}; use crate::ln::channel_state::ChannelDetails; use crate::types::features::{Bolt12InvoiceFeatures, ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures}; #[cfg(any(feature = "_test_utils", test))] @@ -59,7 +58,7 @@ use crate::ln::onion_payment::{check_incoming_htlc_cltv, create_recv_pending_htl use crate::ln::msgs; use crate::ln::onion_utils; use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING}; -use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError}; +use crate::ln::msgs::{ChannelMessageHandler, CommitmentUpdate, DecodeError, LightningError}; #[cfg(test)] use crate::ln::outbound_payment; use crate::ln::outbound_payment::{OutboundPayments, PendingOutboundPayment, RetryableInvoiceRequest, SendAlongPathArgs, StaleExpiration}; @@ -85,6 +84,7 @@ use crate::util::wakers::{Future, Notifier}; use crate::util::scid_utils::fake_scid; use crate::util::string::UntrustedString; use crate::util::ser::{BigSize, FixedLengthReader, Readable, ReadableArgs, MaybeReadable, Writeable, Writer, VecWriter}; +use crate::util::ser::TransactionU16LenLimited; use crate::util::logger::{Level, Logger, WithContext}; use crate::util::errors::APIError; @@ -1336,9 +1336,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, } ) @@ -1359,11 +1357,22 @@ impl PeerState where SP::Target: SignerProvider { } } +#[derive(Clone)] +pub(super) enum OpenChannelMessage { + V1(msgs::OpenChannel), + V2(msgs::OpenChannelV2), +} + +pub(super) enum OpenChannelMessageRef<'a> { + V1(&'a msgs::OpenChannel), + V2(&'a msgs::OpenChannelV2), +} + /// A not-yet-accepted inbound (from counterparty) channel. Once /// accepted, the parameters will be used to construct a channel. pub(super) struct InboundChannelRequest { /// The original OpenChannel message. - pub open_channel_msg: msgs::OpenChannel, + pub open_channel_msg: OpenChannelMessage, /// The number of ticks remaining before the request expires. pub ticks_remaining: i32, } @@ -2591,8 +2600,14 @@ where /// offer they resolve to to the given one. pub testing_dnssec_proof_offer_resolution_override: Mutex>, + #[cfg(test)] + pub(super) entropy_source: ES, + #[cfg(not(test))] entropy_source: ES, node_signer: NS, + #[cfg(test)] + pub(super) signer_provider: SP, + #[cfg(not(test))] signer_provider: SP, logger: L, @@ -3009,11 +3024,9 @@ macro_rules! convert_chan_phase_err { ChannelPhase::UnfundedInboundV1(channel) => { convert_chan_phase_err!($self, $peer_state, $err, channel, $channel_id, UNFUNDED_CHANNEL) }, - #[cfg(any(dual_funding, splicing))] ChannelPhase::UnfundedOutboundV2(channel) => { convert_chan_phase_err!($self, $peer_state, $err, channel, $channel_id, UNFUNDED_CHANNEL) }, - #[cfg(any(dual_funding, splicing))] ChannelPhase::UnfundedInboundV2(channel) => { convert_chan_phase_err!($self, $peer_state, $err, channel, $channel_id, UNFUNDED_CHANNEL) }, @@ -3157,7 +3170,7 @@ macro_rules! handle_monitor_update_completion { &mut $peer_state.pending_msg_events, $chan, updates.raa, updates.commitment_update, updates.order, updates.accepted_htlcs, updates.pending_update_adds, updates.funding_broadcastable, updates.channel_ready, - updates.announcement_sigs); + updates.announcement_sigs, updates.tx_signatures); if let Some(upd) = channel_update { $peer_state.pending_msg_events.push(upd); } @@ -3462,6 +3475,11 @@ where &self.default_configuration } + #[cfg(test)] + pub fn create_and_insert_outbound_scid_alias_for_test(&self) -> u64 { + self.create_and_insert_outbound_scid_alias() + } + fn create_and_insert_outbound_scid_alias(&self) -> u64 { let height = self.best_block.read().unwrap().height; let mut outbound_scid_alias = 0; @@ -3997,13 +4015,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 @@ -6482,11 +6494,9 @@ where ChannelPhase::UnfundedOutboundV1(chan) => { process_unfunded_channel_tick!(peer_state, chan, pending_msg_events) }, - #[cfg(any(dual_funding, splicing))] ChannelPhase::UnfundedInboundV2(chan) => { process_unfunded_channel_tick!(peer_state, chan, pending_msg_events) }, - #[cfg(any(dual_funding, splicing))] ChannelPhase::UnfundedOutboundV2(chan) => { process_unfunded_channel_tick!(peer_state, chan, pending_msg_events) }, @@ -7323,7 +7333,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) { @@ -7446,17 +7456,20 @@ where commitment_update: Option, order: RAACommitmentOrder, pending_forwards: Vec<(PendingHTLCInfo, u64)>, pending_update_adds: Vec, funding_broadcastable: Option, - channel_ready: Option, announcement_sigs: Option) - -> (Option<(u64, Option, OutPoint, ChannelId, u128, Vec<(PendingHTLCInfo, u64)>)>, Option<(u64, Vec)>) { + channel_ready: Option, announcement_sigs: Option, + tx_signatures: Option + ) -> (Option<(u64, Option, OutPoint, ChannelId, u128, Vec<(PendingHTLCInfo, u64)>)>, Option<(u64, Vec)>) { let logger = WithChannelContext::from(&self.logger, &channel.context, None); - log_trace!(logger, "Handling channel resumption for channel {} with {} RAA, {} commitment update, {} pending forwards, {} pending update_add_htlcs, {}broadcasting funding, {} channel ready, {} announcement", + log_trace!(logger, "Handling channel resumption for channel {} with {} RAA, {} commitment update, {} pending forwards, {} pending update_add_htlcs, {}broadcasting funding, {} channel ready, {} announcement, {} tx_signatures", &channel.context.channel_id(), if raa.is_some() { "an" } else { "no" }, if commitment_update.is_some() { "a" } else { "no" }, pending_forwards.len(), pending_update_adds.len(), if funding_broadcastable.is_some() { "" } else { "not " }, if channel_ready.is_some() { "sending" } else { "without" }, - if announcement_sigs.is_some() { "sending" } else { "without" }); + if announcement_sigs.is_some() { "sending" } else { "without" }, + if tx_signatures.is_some() { "sending" } else { "without" }, + ); let counterparty_node_id = channel.context.get_counterparty_node_id(); let short_channel_id = channel.context.get_short_channel_id().unwrap_or(channel.context.outbound_scid_alias()); @@ -7483,6 +7496,12 @@ where msg, }); } + if let Some(msg) = tx_signatures { + pending_msg_events.push(events::MessageSendEvent::SendTxSignatures { + node_id: counterparty_node_id, + msg, + }); + } macro_rules! handle_cs { () => { if let Some(update) = commitment_update { @@ -7600,10 +7619,14 @@ where /// for zero confirmations. Instead, `accept_inbound_channel_from_trusted_peer_0conf` must be /// used to accept such channels. /// + /// NOTE: LDK makes no attempt to prevent the counterparty from using non-standard inputs which + /// will prevent the funding transaction from being relayed on the bitcoin network and hence being + /// confirmed. + /// /// [`Event::OpenChannelRequest`]: events::Event::OpenChannelRequest /// [`Event::ChannelClosed::user_channel_id`]: events::Event::ChannelClosed::user_channel_id pub fn accept_inbound_channel(&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, user_channel_id: u128) -> Result<(), APIError> { - self.do_accept_inbound_channel(temporary_channel_id, counterparty_node_id, false, user_channel_id) + self.do_accept_inbound_channel(temporary_channel_id, counterparty_node_id, false, user_channel_id, vec![], Weight::from_wu(0)) } /// Accepts a request to open a channel after a [`events::Event::OpenChannelRequest`], treating @@ -7625,11 +7648,14 @@ where /// [`Event::OpenChannelRequest`]: events::Event::OpenChannelRequest /// [`Event::ChannelClosed::user_channel_id`]: events::Event::ChannelClosed::user_channel_id pub fn accept_inbound_channel_from_trusted_peer_0conf(&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, user_channel_id: u128) -> Result<(), APIError> { - self.do_accept_inbound_channel(temporary_channel_id, counterparty_node_id, true, user_channel_id) + self.do_accept_inbound_channel(temporary_channel_id, counterparty_node_id, true, user_channel_id, vec![], Weight::from_wu(0)) } - fn do_accept_inbound_channel(&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, accept_0conf: bool, user_channel_id: u128) -> Result<(), APIError> { - + fn do_accept_inbound_channel( + &self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, accept_0conf: bool, + user_channel_id: u128, funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>, + total_witness_weight: Weight, + ) -> Result<(), APIError> { let logger = WithContext::from(&self.logger, Some(*counterparty_node_id), Some(*temporary_channel_id), None); let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); @@ -7654,12 +7680,44 @@ where let res = match peer_state.inbound_channel_request_by_id.remove(temporary_channel_id) { Some(unaccepted_channel) => { let best_block_height = self.best_block.read().unwrap().height; - InboundV1Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, - counterparty_node_id.clone(), &self.channel_type_features(), &peer_state.latest_features, - &unaccepted_channel.open_channel_msg, user_channel_id, &self.default_configuration, best_block_height, - &self.logger, accept_0conf).map_err(|err| MsgHandleErrInternal::from_chan_no_close(err, *temporary_channel_id)) + match unaccepted_channel.open_channel_msg { + OpenChannelMessage::V1(open_channel_msg) => { + InboundV1Channel::new( + &self.fee_estimator, &self.entropy_source, &self.signer_provider, *counterparty_node_id, + &self.channel_type_features(), &peer_state.latest_features, &open_channel_msg, + user_channel_id, &self.default_configuration, best_block_height, &self.logger, accept_0conf + ).map_err(|err| MsgHandleErrInternal::from_chan_no_close(err, *temporary_channel_id) + ).map(|channel| { + let message_send_event = events::MessageSendEvent::SendAcceptChannel { + node_id: *counterparty_node_id, + msg: channel.accept_inbound_channel(), + }; + (*temporary_channel_id, ChannelPhase::UnfundedInboundV1(channel), message_send_event) + }) + }, + OpenChannelMessage::V2(open_channel_msg) => { + InboundV2Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, + self.get_our_node_id(), *counterparty_node_id, &self.channel_type_features(), &peer_state.latest_features, + &open_channel_msg, funding_inputs, total_witness_weight, user_channel_id, + &self.default_configuration, best_block_height, &self.logger + ).map_err(|_| MsgHandleErrInternal::from_chan_no_close( + ChannelError::Close( + ( + "V2 channel rejected due to sender error".into(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ) + ), *temporary_channel_id) + ).map(|channel| { + let message_send_event = events::MessageSendEvent::SendAcceptChannelV2 { + node_id: channel.context.get_counterparty_node_id(), + msg: channel.accept_inbound_dual_funded_channel() + }; + (channel.context.channel_id(), ChannelPhase::UnfundedInboundV2(channel), message_send_event) + }) + }, + } }, - _ => { + None => { let err_str = "No such channel awaiting to be accepted.".to_owned(); log_error!(logger, "{}", err_str); @@ -7667,10 +7725,14 @@ where } }; - match res { + // We have to match below instead of map_err on the above as in the map_err closure the borrow checker + // would consider peer_state moved even though we would bail out with the `?` operator. + let (channel_id, mut channel_phase, message_send_event) = match res { + Ok(res) => res, Err(err) => { mem::drop(peer_state_lock); mem::drop(per_peer_state); + // TODO(dunxen): Find/make less icky way to do this. match handle_error!(self, Result::<(), MsgHandleErrInternal>::Err(err), *counterparty_node_id) { Ok(_) => unreachable!("`handle_error` only returns Err as we've passed in an Err"), Err(e) => { @@ -7678,55 +7740,50 @@ where }, } } - Ok(mut channel) => { - if accept_0conf { - // This should have been correctly configured by the call to InboundV1Channel::new. - debug_assert!(channel.context.minimum_depth().unwrap() == 0); - } else if channel.context.get_channel_type().requires_zero_conf() { - let send_msg_err_event = events::MessageSendEvent::HandleError { - node_id: channel.context.get_counterparty_node_id(), - action: msgs::ErrorAction::SendErrorMessage{ - msg: msgs::ErrorMessage { channel_id: temporary_channel_id.clone(), data: "No zero confirmation channels accepted".to_owned(), } - } - }; - peer_state.pending_msg_events.push(send_msg_err_event); - let err_str = "Please use accept_inbound_channel_from_trusted_peer_0conf to accept channels with zero confirmations.".to_owned(); - log_error!(logger, "{}", err_str); + }; - return Err(APIError::APIMisuseError { err: err_str }); - } else { - // If this peer already has some channels, a new channel won't increase our number of peers - // with unfunded channels, so as long as we aren't over the maximum number of unfunded - // channels per-peer we can accept channels from a peer with existing ones. - if is_only_peer_channel && peers_without_funded_channels >= MAX_UNFUNDED_CHANNEL_PEERS { - let send_msg_err_event = events::MessageSendEvent::HandleError { - node_id: channel.context.get_counterparty_node_id(), - action: msgs::ErrorAction::SendErrorMessage{ - msg: msgs::ErrorMessage { channel_id: temporary_channel_id.clone(), data: "Have too many peers with unfunded channels, not accepting new ones".to_owned(), } - } - }; - peer_state.pending_msg_events.push(send_msg_err_event); - let err_str = "Too many peers with unfunded channels, refusing to accept new ones".to_owned(); - log_error!(logger, "{}", err_str); + if accept_0conf { + // This should have been correctly configured by the call to Inbound(V1/V2)Channel::new. + debug_assert!(channel_phase.context().minimum_depth().unwrap() == 0); + } else if channel_phase.context().get_channel_type().requires_zero_conf() { + let send_msg_err_event = events::MessageSendEvent::HandleError { + node_id: channel_phase.context().get_counterparty_node_id(), + action: msgs::ErrorAction::SendErrorMessage{ + msg: msgs::ErrorMessage { channel_id: *temporary_channel_id, data: "No zero confirmation channels accepted".to_owned(), } + } + }; + peer_state.pending_msg_events.push(send_msg_err_event); + let err_str = "Please use accept_inbound_channel_from_trusted_peer_0conf to accept channels with zero confirmations.".to_owned(); + log_error!(logger, "{}", err_str); - return Err(APIError::APIMisuseError { err: err_str }); + return Err(APIError::APIMisuseError { err: err_str }); + } else { + // If this peer already has some channels, a new channel won't increase our number of peers + // with unfunded channels, so as long as we aren't over the maximum number of unfunded + // channels per-peer we can accept channels from a peer with existing ones. + if is_only_peer_channel && peers_without_funded_channels >= MAX_UNFUNDED_CHANNEL_PEERS { + let send_msg_err_event = events::MessageSendEvent::HandleError { + node_id: channel_phase.context().get_counterparty_node_id(), + action: msgs::ErrorAction::SendErrorMessage{ + msg: msgs::ErrorMessage { channel_id: *temporary_channel_id, data: "Have too many peers with unfunded channels, not accepting new ones".to_owned(), } } - } + }; + peer_state.pending_msg_events.push(send_msg_err_event); + let err_str = "Too many peers with unfunded channels, refusing to accept new ones".to_owned(); + log_error!(logger, "{}", err_str); - // Now that we know we have a channel, assign an outbound SCID alias. - let outbound_scid_alias = self.create_and_insert_outbound_scid_alias(); - channel.context.set_outbound_scid_alias(outbound_scid_alias); + return Err(APIError::APIMisuseError { err: err_str }); + } + } - peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel { - node_id: channel.context.get_counterparty_node_id(), - msg: channel.accept_inbound_channel(), - }); + // Now that we know we have a channel, assign an outbound SCID alias. + let outbound_scid_alias = self.create_and_insert_outbound_scid_alias(); + channel_phase.context_mut().set_outbound_scid_alias(outbound_scid_alias); - peer_state.channel_by_id.insert(temporary_channel_id.clone(), ChannelPhase::UnfundedInboundV1(channel)); + peer_state.pending_msg_events.push(message_send_event); + peer_state.channel_by_id.insert(channel_id, channel_phase); - Ok(()) - }, - } + Ok(()) } /// Gets the number of peers which match the given filter and do not have any funded, outbound, @@ -7772,8 +7829,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. @@ -7782,32 +7837,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 common_fields = match msg { + OpenChannelMessageRef::V1(msg) => &msg.common_fields, + OpenChannelMessageRef::V2(msg) => &msg.common_fields, + }; + + // Do common open_channel(2) checks + // Note that the ChannelManager is NOT re-persisted on disk after this, so any changes are // likely to be lost on restart! - if msg.common_fields.chain_hash != self.chain_hash { + if common_fields.chain_hash != self.chain_hash { return Err(MsgHandleErrInternal::send_err_msg_no_close("Unknown genesis block hash".to_owned(), - msg.common_fields.temporary_channel_id.clone())); + common_fields.temporary_channel_id)); } if !self.default_configuration.accept_inbound_channels { return Err(MsgHandleErrInternal::send_err_msg_no_close("No inbound channels accepted".to_owned(), - msg.common_fields.temporary_channel_id.clone())); + common_fields.temporary_channel_id)); } // Get the number of peers with channels, but without funded ones. We don't care too much @@ -7822,7 +7878,7 @@ where debug_assert!(false); MsgHandleErrInternal::send_err_msg_no_close( format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), - msg.common_fields.temporary_channel_id.clone()) + common_fields.temporary_channel_id) })?; let mut peer_state_lock = peer_state_mutex.lock().unwrap(); let peer_state = &mut *peer_state_lock; @@ -7836,44 +7892,51 @@ where { return Err(MsgHandleErrInternal::send_err_msg_no_close( "Have too many peers with unfunded channels, not accepting new ones".to_owned(), - msg.common_fields.temporary_channel_id.clone())); + common_fields.temporary_channel_id)); } let best_block_height = self.best_block.read().unwrap().height; if Self::unfunded_channel_count(peer_state, best_block_height) >= MAX_UNFUNDED_CHANS_PER_PEER { return Err(MsgHandleErrInternal::send_err_msg_no_close( format!("Refusing more than {} unfunded channels.", MAX_UNFUNDED_CHANS_PER_PEER), - msg.common_fields.temporary_channel_id.clone())); + common_fields.temporary_channel_id)); } - let channel_id = msg.common_fields.temporary_channel_id; + let channel_id = common_fields.temporary_channel_id; let channel_exists = peer_state.has_channel(&channel_id); if channel_exists { return Err(MsgHandleErrInternal::send_err_msg_no_close( "temporary_channel_id collision for the same peer!".to_owned(), - msg.common_fields.temporary_channel_id.clone())); + common_fields.temporary_channel_id)); } + // We can get the channel type at this point already as we'll need it immediately in both the + // manual and the automatic acceptance cases. + let channel_type = channel::channel_type_from_open_channel( + common_fields, &peer_state.latest_features, &self.channel_type_features() + ).map_err(|e| MsgHandleErrInternal::from_chan_no_close(e, common_fields.temporary_channel_id))?; + // If we're doing manual acceptance checks on the channel, then defer creation until we're sure we want to accept. if self.default_configuration.manually_accept_inbound_channels { - let channel_type = channel::channel_type_from_open_channel( - &msg.common_fields, &peer_state.latest_features, &self.channel_type_features() - ).map_err(|e| - MsgHandleErrInternal::from_chan_no_close(e, msg.common_fields.temporary_channel_id) - )?; let mut pending_events = self.pending_events.lock().unwrap(); - let is_announced = (msg.common_fields.channel_flags & 1) == 1; + let is_announced = (common_fields.channel_flags & 1) == 1; pending_events.push_back((events::Event::OpenChannelRequest { - temporary_channel_id: msg.common_fields.temporary_channel_id.clone(), - counterparty_node_id: counterparty_node_id.clone(), - funding_satoshis: msg.common_fields.funding_satoshis, - push_msat: msg.push_msat, + temporary_channel_id: common_fields.temporary_channel_id, + counterparty_node_id: *counterparty_node_id, + funding_satoshis: common_fields.funding_satoshis, + channel_negotiation_type: match msg { + OpenChannelMessageRef::V1(msg) => InboundChannelFunds::PushMsat(msg.push_msat), + OpenChannelMessageRef::V2(_) => InboundChannelFunds::DualFunded, + }, channel_type, is_announced, - params: msg.common_fields.channel_parameters(), + params: common_fields.channel_parameters(), }, None)); peer_state.inbound_channel_request_by_id.insert(channel_id, InboundChannelRequest { - open_channel_msg: msg.clone(), + open_channel_msg: match msg { + OpenChannelMessageRef::V1(msg) => OpenChannelMessage::V1(msg.clone()), + OpenChannelMessageRef::V2(msg) => OpenChannelMessage::V2(msg.clone()), + }, ticks_remaining: UNACCEPTED_INBOUND_CHANNEL_AGE_LIMIT_TICKS, }); return Ok(()); @@ -7883,36 +7946,47 @@ where let mut random_bytes = [0u8; 16]; random_bytes.copy_from_slice(&self.entropy_source.get_secure_random_bytes()[..16]); let user_channel_id = u128::from_be_bytes(random_bytes); - let mut channel = match InboundV1Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, - counterparty_node_id.clone(), &self.channel_type_features(), &peer_state.latest_features, msg, user_channel_id, - &self.default_configuration, best_block_height, &self.logger, /*is_0conf=*/false) - { - Err(e) => { - return Err(MsgHandleErrInternal::from_chan_no_close(e, msg.common_fields.temporary_channel_id)); - }, - Ok(res) => res - }; - let channel_type = channel.context.get_channel_type(); if channel_type.requires_zero_conf() { - return Err(MsgHandleErrInternal::send_err_msg_no_close( - "No zero confirmation channels accepted".to_owned(), - msg.common_fields.temporary_channel_id.clone())); + return Err(MsgHandleErrInternal::send_err_msg_no_close("No zero confirmation channels accepted".to_owned(), common_fields.temporary_channel_id)); } if channel_type.requires_anchors_zero_fee_htlc_tx() { - return Err(MsgHandleErrInternal::send_err_msg_no_close( - "No channels with anchor outputs accepted".to_owned(), - msg.common_fields.temporary_channel_id.clone())); - } + return Err(MsgHandleErrInternal::send_err_msg_no_close("No channels with anchor outputs accepted".to_owned(), common_fields.temporary_channel_id)); + } + + let (mut channel_phase, message_send_event) = match msg { + OpenChannelMessageRef::V1(msg) => { + let channel = InboundV1Channel::new( + &self.fee_estimator, &self.entropy_source, &self.signer_provider, *counterparty_node_id, + &self.channel_type_features(), &peer_state.latest_features, msg, user_channel_id, + &self.default_configuration, best_block_height, &self.logger, /*is_0conf=*/false + ).map_err(|e| MsgHandleErrInternal::from_chan_no_close(e, msg.common_fields.temporary_channel_id))?; + let message_send_event = events::MessageSendEvent::SendAcceptChannel { + node_id: *counterparty_node_id, + msg: channel.accept_inbound_channel(), + }; + (ChannelPhase::UnfundedInboundV1(channel), message_send_event) + }, + OpenChannelMessageRef::V2(msg) => { + let channel = InboundV2Channel::new(&self.fee_estimator, &self.entropy_source, + &self.signer_provider, self.get_our_node_id(), *counterparty_node_id, + &self.channel_type_features(), &peer_state.latest_features, msg, vec![], Weight::from_wu(0), + user_channel_id, &self.default_configuration, best_block_height, &self.logger + ).map_err(|e| MsgHandleErrInternal::from_chan_no_close(e, msg.common_fields.temporary_channel_id))?; + let message_send_event = events::MessageSendEvent::SendAcceptChannelV2 { + node_id: *counterparty_node_id, + msg: channel.accept_inbound_dual_funded_channel(), + }; + (ChannelPhase::UnfundedInboundV2(channel), message_send_event) + }, + }; let outbound_scid_alias = self.create_and_insert_outbound_scid_alias(); - channel.context.set_outbound_scid_alias(outbound_scid_alias); + channel_phase.context_mut().set_outbound_scid_alias(outbound_scid_alias); + + peer_state.pending_msg_events.push(message_send_event); + peer_state.channel_by_id.insert(channel_phase.context().channel_id(), channel_phase); - peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel { - node_id: counterparty_node_id.clone(), - msg: channel.accept_inbound_channel(), - }); - peer_state.channel_by_id.insert(channel_id, ChannelPhase::UnfundedInboundV1(channel)); Ok(()) } @@ -7932,7 +8006,7 @@ where hash_map::Entry::Occupied(mut phase) => { match phase.get_mut() { ChannelPhase::UnfundedOutboundV1(chan) => { - try_chan_phase_entry!(self, peer_state, chan.accept_channel(&msg, &self.default_configuration.channel_handshake_limits, &peer_state.latest_features), phase); + try_chan_phase_entry!(self, peer_state, chan.accept_channel(msg, &self.default_configuration.channel_handshake_limits, &peer_state.latest_features), phase); (chan.context.get_value_satoshis(), chan.context.get_funding_redeemscript().to_p2wsh(), chan.context.get_user_id()) }, _ => { @@ -8112,6 +8186,284 @@ where } } + fn internal_tx_msg) -> Result>( + &self, counterparty_node_id: &PublicKey, channel_id: ChannelId, tx_msg_handler: HandleTxMsgFn + ) -> Result<(), MsgHandleErrInternal> { + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| { + debug_assert!(false); + MsgHandleErrInternal::send_err_msg_no_close( + format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), + channel_id) + })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + match peer_state.channel_by_id.entry(channel_id) { + hash_map::Entry::Occupied(mut chan_phase_entry) => { + let channel_phase = chan_phase_entry.get_mut(); + let msg_send_event = match tx_msg_handler(channel_phase) { + Ok(msg_send_event) => msg_send_event, + Err(tx_msg_str) => return Err(MsgHandleErrInternal::from_chan_no_close(ChannelError::Warn( + format!("Got a {tx_msg_str} message with no interactive transaction construction expected or in-progress") + ), channel_id)), + }; + peer_state.pending_msg_events.push(msg_send_event); + Ok(()) + }, + hash_map::Entry::Vacant(_) => { + Err(MsgHandleErrInternal::send_err_msg_no_close(format!( + "Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", + counterparty_node_id), channel_id) + ) + } + } + } + + fn internal_tx_add_input(&self, counterparty_node_id: PublicKey, msg: &msgs::TxAddInput) -> Result<(), MsgHandleErrInternal> { + self.internal_tx_msg(&counterparty_node_id, msg.channel_id, |channel_phase: &mut ChannelPhase| { + match channel_phase { + ChannelPhase::UnfundedInboundV2(ref mut channel) => { + Ok(channel.tx_add_input(msg).into_msg_send_event(counterparty_node_id)) + }, + ChannelPhase::UnfundedOutboundV2(ref mut channel) => { + Ok(channel.tx_add_input(msg).into_msg_send_event(counterparty_node_id)) + }, + _ => Err("tx_add_input"), + } + }) + } + + fn internal_tx_add_output(&self, counterparty_node_id: PublicKey, msg: &msgs::TxAddOutput) -> Result<(), MsgHandleErrInternal> { + self.internal_tx_msg(&counterparty_node_id, msg.channel_id, |channel_phase: &mut ChannelPhase| { + match channel_phase { + ChannelPhase::UnfundedInboundV2(ref mut channel) => { + Ok(channel.tx_add_output(msg).into_msg_send_event(counterparty_node_id)) + }, + ChannelPhase::UnfundedOutboundV2(ref mut channel) => { + Ok(channel.tx_add_output(msg).into_msg_send_event(counterparty_node_id)) + }, + _ => Err("tx_add_output"), + } + }) + } + + fn internal_tx_remove_input(&self, counterparty_node_id: PublicKey, msg: &msgs::TxRemoveInput) -> Result<(), MsgHandleErrInternal> { + self.internal_tx_msg(&counterparty_node_id, msg.channel_id, |channel_phase: &mut ChannelPhase| { + match channel_phase { + ChannelPhase::UnfundedInboundV2(ref mut channel) => { + Ok(channel.tx_remove_input(msg).into_msg_send_event(counterparty_node_id)) + }, + ChannelPhase::UnfundedOutboundV2(ref mut channel) => { + Ok(channel.tx_remove_input(msg).into_msg_send_event(counterparty_node_id)) + }, + _ => Err("tx_remove_input"), + } + }) + } + + fn internal_tx_remove_output(&self, counterparty_node_id: PublicKey, msg: &msgs::TxRemoveOutput) -> Result<(), MsgHandleErrInternal> { + self.internal_tx_msg(&counterparty_node_id, msg.channel_id, |channel_phase: &mut ChannelPhase| { + match channel_phase { + ChannelPhase::UnfundedInboundV2(ref mut channel) => { + Ok(channel.tx_remove_output(msg).into_msg_send_event(counterparty_node_id)) + }, + ChannelPhase::UnfundedOutboundV2(ref mut channel) => { + Ok(channel.tx_remove_output(msg).into_msg_send_event(counterparty_node_id)) + }, + _ => Err("tx_remove_output"), + } + }) + } + + fn internal_tx_complete(&self, counterparty_node_id: PublicKey, msg: &msgs::TxComplete) -> Result<(), MsgHandleErrInternal> { + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(&counterparty_node_id) + .ok_or_else(|| { + debug_assert!(false); + MsgHandleErrInternal::send_err_msg_no_close( + format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), + msg.channel_id) + })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + match peer_state.channel_by_id.entry(msg.channel_id) { + hash_map::Entry::Occupied(mut chan_phase_entry) => { + let channel_phase = chan_phase_entry.get_mut(); + let (msg_send_event_opt, signing_session_opt) = match channel_phase { + ChannelPhase::UnfundedInboundV2(channel) => channel.tx_complete(msg) + .into_msg_send_event_or_signing_session(counterparty_node_id), + ChannelPhase::UnfundedOutboundV2(channel) => channel.tx_complete(msg) + .into_msg_send_event_or_signing_session(counterparty_node_id), + _ => try_chan_phase_entry!(self, peer_state, Err(ChannelError::Close( + ( + "Got a tx_complete message with no interactive transaction construction expected or in-progress".into(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))), chan_phase_entry) + }; + if let Some(msg_send_event) = msg_send_event_opt { + peer_state.pending_msg_events.push(msg_send_event); + }; + if let Some(mut signing_session) = signing_session_opt { + let (commitment_signed, funding_ready_for_sig_event_opt) = match chan_phase_entry.get_mut() { + ChannelPhase::UnfundedOutboundV2(chan) => { + chan.funding_tx_constructed(&mut signing_session, &self.logger) + }, + ChannelPhase::UnfundedInboundV2(chan) => { + chan.funding_tx_constructed(&mut signing_session, &self.logger) + }, + _ => Err(ChannelError::Warn( + "Got a tx_complete message with no interactive transaction construction expected or in-progress" + .into())), + }.map_err(|err| MsgHandleErrInternal::send_err_msg_no_close(format!("{}", err), msg.channel_id))?; + let (channel_id, channel_phase) = chan_phase_entry.remove_entry(); + let channel = match channel_phase { + ChannelPhase::UnfundedOutboundV2(chan) => chan.into_channel(signing_session), + ChannelPhase::UnfundedInboundV2(chan) => chan.into_channel(signing_session), + _ => { + debug_assert!(false); // It cannot be another variant as we are in the `Ok` branch of the above match. + Err(ChannelError::Warn( + "Got a tx_complete message with no interactive transaction construction expected or in-progress" + .into())) + }, + }.map_err(|err| MsgHandleErrInternal::send_err_msg_no_close(format!("{}", err), msg.channel_id))?; + peer_state.channel_by_id.insert(channel_id, ChannelPhase::Funded(channel)); + if let Some(funding_ready_for_sig_event) = funding_ready_for_sig_event_opt { + let mut pending_events = self.pending_events.lock().unwrap(); + pending_events.push_back((funding_ready_for_sig_event, None)); + } + peer_state.pending_msg_events.push(events::MessageSendEvent::UpdateHTLCs { + node_id: counterparty_node_id, + updates: CommitmentUpdate { + commitment_signed, + update_add_htlcs: vec![], + update_fulfill_htlcs: vec![], + update_fail_htlcs: vec![], + update_fail_malformed_htlcs: vec![], + update_fee: None, + }, + }); + } + Ok(()) + }, + 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 logger = WithChannelContext::from(&self.logger, &chan.context, None); + let (tx_signatures_opt, funding_tx_opt) = try_chan_phase_entry!(self, peer_state, chan.tx_signatures(msg, &&logger), chan_phase_entry); + if let Some(tx_signatures) = tx_signatures_opt { + peer_state.pending_msg_events.push(events::MessageSendEvent::SendTxSignatures { + node_id: *counterparty_node_id, + msg: tx_signatures, + }); + } + if let Some(ref funding_tx) = funding_tx_opt { + self.tx_broadcaster.broadcast_transactions(&[funding_tx]); + { + let mut pending_events = self.pending_events.lock().unwrap(); + emit_channel_pending_event!(pending_events, chan); + } + } + }, + _ => try_chan_phase_entry!(self, peer_state, Err(ChannelError::Close( + ( + "Got an unexpected tx_signatures message".into(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))), chan_phase_entry) + } + Ok(()) + }, + hash_map::Entry::Vacant(_) => { + Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id)) + } + } + } + + fn internal_tx_abort(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxAbort) + -> Result<(), MsgHandleErrInternal> { + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| { + debug_assert!(false); + MsgHandleErrInternal::send_err_msg_no_close( + format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), + msg.channel_id) + })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + match peer_state.channel_by_id.entry(msg.channel_id) { + hash_map::Entry::Occupied(mut chan_phase_entry) => { + let channel_phase = chan_phase_entry.get_mut(); + let tx_constructor = match channel_phase { + ChannelPhase::UnfundedInboundV2(chan) => chan.interactive_tx_constructor_mut(), + ChannelPhase::UnfundedOutboundV2(chan) => chan.interactive_tx_constructor_mut(), + ChannelPhase::Funded(_) => { + // TODO(splicing)/TODO(RBF): We'll also be doing interactive tx construction + // for a "ChannelPhase::Funded" when we want to bump the fee on an interactively + // constructed funding tx or during splicing. For now we send an error as we would + // never ack an RBF attempt or a splice for now: + try_chan_phase_entry!(self, peer_state, Err(ChannelError::Warn( + "Got an unexpected tx_abort message: After initial funding transaction is signed, \ + splicing and RBF attempts of interactive funding transactions are not supported yet so \ + we don't have any negotiation in progress".into(), + )), chan_phase_entry) + } + ChannelPhase::UnfundedInboundV1(_) | ChannelPhase::UnfundedOutboundV1(_) => { + try_chan_phase_entry!(self, peer_state, Err(ChannelError::Warn( + "Got an unexpected tx_abort message: This is an unfunded channel created with V1 channel \ + establishment".into(), + )), chan_phase_entry) + }, + }; + // This checks for and resets the interactive negotiation state by `take()`ing it from the channel. + // The existence of the `tx_constructor` indicates that we have not moved into the signing + // phase for this interactively constructed transaction and hence we have not exchanged + // `tx_signatures`. Either way, we never close the channel upon receiving a `tx_abort`: + // https://github.com/lightning/bolts/blob/247e83d/02-peer-protocol.md?plain=1#L574-L576 + if tx_constructor.take().is_some() { + let msg = msgs::TxAbort { + channel_id: msg.channel_id, + data: "Acknowledged tx_abort".to_string().into_bytes(), + }; + // NOTE: Since at this point we have not sent a `tx_abort` message for this negotiation + // previously (tx_constructor was `Some`), we need to echo back a tx_abort message according + // to the spec: + // https://github.com/lightning/bolts/blob/247e83d/02-peer-protocol.md?plain=1#L560-L561 + // For rationale why we echo back `tx_abort`: + // https://github.com/lightning/bolts/blob/247e83d/02-peer-protocol.md?plain=1#L578-L580 + peer_state.pending_msg_events.push(events::MessageSendEvent::SendTxAbort { + node_id: *counterparty_node_id, + msg, + }); + } + Ok(()) + }, + hash_map::Entry::Vacant(_) => { + Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id)) + } + } + } + fn internal_channel_ready(&self, counterparty_node_id: &PublicKey, msg: &msgs::ChannelReady) -> Result<(), MsgHandleErrInternal> { // 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! @@ -8210,17 +8562,8 @@ where peer_state_lock, peer_state, per_peer_state, chan); } }, - ChannelPhase::UnfundedInboundV1(_) | ChannelPhase::UnfundedOutboundV1(_) => { - 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, peer_state, 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::UnfundedInboundV1(_) | ChannelPhase::UnfundedOutboundV1(_) | 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, peer_state, chan_phase_entry); finish_shutdown = Some(chan.context_mut().force_shutdown(false, ClosureReason::CounterpartyCoopClosedUnfundedChannel)); @@ -8487,6 +8830,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(|| { @@ -8500,10 +8844,32 @@ where if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() { let logger = WithChannelContext::from(&self.logger, &chan.context, None); let funding_txo = chan.context.get_funding_txo(); - let monitor_update_opt = try_chan_phase_entry!(self, peer_state, chan.commitment_signed(&msg, &&logger), chan_phase_entry); - if let Some(monitor_update) = monitor_update_opt { - handle_new_monitor_update!(self, funding_txo.unwrap(), monitor_update, peer_state_lock, - peer_state, per_peer_state, chan); + + if chan.interactive_tx_signing_session.is_some() { + let monitor = try_chan_phase_entry!( + self, peer_state, chan.commitment_signed_initial_v2(msg, best_block, &self.signer_provider, &&logger), + chan_phase_entry); + let monitor_res = self.chain_monitor.watch_channel(monitor.get_funding_txo().0, monitor); + if let Ok(persist_state) = monitor_res { + handle_new_monitor_update!(self, persist_state, peer_state_lock, peer_state, + per_peer_state, chan, INITIAL_MONITOR); + } else { + let logger = WithChannelContext::from(&self.logger, &chan.context, None); + log_error!(logger, "Persisting initial ChannelMonitor failed, implying the funding outpoint was duplicated"); + try_chan_phase_entry!(self, peer_state, Err(ChannelError::Close( + ( + "Channel funding outpoint was a duplicate".to_owned(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ) + )), chan_phase_entry) + } + } else { + let monitor_update_opt = try_chan_phase_entry!( + self, peer_state, chan.commitment_signed(msg, &&logger), chan_phase_entry); + if let Some(monitor_update) = monitor_update_opt { + handle_new_monitor_update!(self, funding_txo.unwrap(), monitor_update, peer_state_lock, + peer_state, per_peer_state, chan); + } } Ok(()) } else { @@ -8511,7 +8877,7 @@ where "Got a commitment_signed message for an unfunded channel!".into())), chan_phase_entry); } }, - hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id)) + hash_map::Entry::Vacant(_) => Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id)) } } @@ -8877,7 +9243,7 @@ where let need_lnd_workaround = chan.context.workaround_lnd_bug_4006.take(); let (htlc_forwards, decode_update_add_htlcs) = self.handle_channel_resumption( &mut peer_state.pending_msg_events, chan, responses.raa, responses.commitment_update, responses.order, - Vec::new(), Vec::new(), None, responses.channel_ready, responses.announcement_sigs); + Vec::new(), Vec::new(), None, responses.channel_ready, responses.announcement_sigs, None); debug_assert!(htlc_forwards.is_none()); debug_assert!(decode_update_add_htlcs.is_none()); if let Some(upd) = channel_update { @@ -9145,7 +9511,7 @@ where } None } - ChannelPhase::UnfundedInboundV1(_) => None, + ChannelPhase::UnfundedInboundV1(_) | ChannelPhase::UnfundedInboundV2(_) | ChannelPhase::UnfundedOutboundV2(_) => None, } }; @@ -10601,9 +10967,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); @@ -10869,7 +11233,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"); @@ -10883,9 +11247,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) { @@ -11111,11 +11487,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 }, @@ -11277,8 +11651,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(), @@ -11286,21 +11658,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); - }, } } } @@ -11397,7 +11760,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 { @@ -11407,9 +11769,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(_)) => (), } } @@ -11431,39 +11791,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_output message - interactive transaction construction does not need to + // be persisted before any signatures are exchanged. + let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || { + let _ = handle_error!(self, self.internal_tx_add_output(counterparty_node_id, msg), counterparty_node_id); + NotifyOption::SkipPersistHandleEvents + }); } fn handle_tx_remove_input(&self, counterparty_node_id: PublicKey, msg: &msgs::TxRemoveInput) { - let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( - "Dual-funded channels not supported".to_owned(), - msg.channel_id.clone())), counterparty_node_id); + // Note that we never need to persist the updated ChannelManager for an inbound + // tx_remove_input message - interactive transaction construction does not need to + // be persisted before any signatures are exchanged. + let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || { + let _ = handle_error!(self, self.internal_tx_remove_input(counterparty_node_id, msg), counterparty_node_id); + NotifyOption::SkipPersistHandleEvents + }); } fn handle_tx_remove_output(&self, counterparty_node_id: PublicKey, msg: &msgs::TxRemoveOutput) { - let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( - "Dual-funded channels not supported".to_owned(), - msg.channel_id.clone())), counterparty_node_id); + // Note that we never need to persist the updated ChannelManager for an inbound + // tx_remove_output message - interactive transaction construction does not need to + // be persisted before any signatures are exchanged. + let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || { + let _ = handle_error!(self, self.internal_tx_remove_output(counterparty_node_id, msg), counterparty_node_id); + NotifyOption::SkipPersistHandleEvents + }); } fn handle_tx_complete(&self, counterparty_node_id: PublicKey, msg: &msgs::TxComplete) { - let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( - "Dual-funded channels not supported".to_owned(), - msg.channel_id.clone())), counterparty_node_id); + // Note that we never need to persist the updated ChannelManager for an inbound + // tx_complete message - interactive transaction construction does not need to + // be persisted before any signatures are exchanged. + let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || { + let _ = handle_error!(self, self.internal_tx_complete(counterparty_node_id, msg), counterparty_node_id); + NotifyOption::SkipPersistHandleEvents + }); } fn handle_tx_signatures(&self, counterparty_node_id: PublicKey, msg: &msgs::TxSignatures) { - 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) { @@ -11479,9 +11858,13 @@ where } fn handle_tx_abort(&self, counterparty_node_id: PublicKey, msg: &msgs::TxAbort) { - let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( - "Dual-funded channels not supported".to_owned(), - msg.channel_id.clone())), counterparty_node_id); + // Note that we never need to persist the updated ChannelManager for an inbound + // tx_abort message - interactive transaction construction does not need to + // be persisted before any signatures are exchanged. + let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || { + let _ = handle_error!(self, self.internal_tx_abort(&counterparty_node_id, msg), counterparty_node_id); + NotifyOption::SkipPersistHandleEvents + }); } fn message_received(&self) { @@ -11925,6 +12308,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 } diff --git a/lightning/src/ln/dual_funding_tests.rs b/lightning/src/ln/dual_funding_tests.rs new file mode 100644 index 00000000000..7742931cd9f --- /dev/null +++ b/lightning/src/ln/dual_funding_tests.rs @@ -0,0 +1,266 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! Tests that test the creation of dual-funded channels in ChannelManager. + +use bitcoin::Weight; + +use crate::chain::chaininterface::{ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator}; +use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider}; +use crate::ln::chan_utils::{ + make_funding_redeemscript, ChannelPublicKeys, ChannelTransactionParameters, + CounterpartyChannelTransactionParameters, +}; +use crate::ln::channel::{ + calculate_our_funding_satoshis, OutboundV2Channel, MIN_CHAN_DUST_LIMIT_SATOSHIS, +}; +use crate::ln::channel_keys::{DelayedPaymentBasepoint, HtlcBasepoint, RevocationBasepoint}; +use crate::ln::functional_test_utils::*; +use crate::ln::msgs::ChannelMessageHandler; +use crate::ln::msgs::{CommitmentSigned, TxAddInput, TxAddOutput, TxComplete}; +use crate::ln::types::ChannelId; +use crate::prelude::*; +use crate::sign::{ChannelSigner as _, P2WPKH_WITNESS_WEIGHT}; +use crate::util::ser::TransactionU16LenLimited; +use crate::util::test_utils; + +// Dual-funding: V2 Channel Establishment Tests +struct V2ChannelEstablishmentTestSession { + initiator_input_value_satoshis: u64, +} + +// TODO(dual_funding): Use real node and API for creating V2 channels as initiator when available, +// instead of manually constructing messages. +fn do_test_v2_channel_establishment( + session: V2ChannelEstablishmentTestSession, test_async_persist: bool, +) { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + let logger_a = test_utils::TestLogger::with_id("node a".to_owned()); + + // Create a funding input for the new channel along with its previous transaction. + let initiator_funding_inputs: Vec<_> = create_dual_funding_utxos_with_prev_txs( + &nodes[0], + &[session.initiator_input_value_satoshis], + ) + .into_iter() + .map(|(txin, tx)| (txin, TransactionU16LenLimited::new(tx).unwrap())) + .collect(); + + // Alice creates a dual-funded channel as initiator. + let funding_feerate = node_cfgs[0] + .fee_estimator + .get_est_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee); + let funding_satoshis = calculate_our_funding_satoshis( + true, + &initiator_funding_inputs[..], + Weight::from_wu(P2WPKH_WITNESS_WEIGHT), + funding_feerate, + MIN_CHAN_DUST_LIMIT_SATOSHIS, + ) + .unwrap(); + let mut channel = OutboundV2Channel::new( + &LowerBoundedFeeEstimator(node_cfgs[0].fee_estimator), + &nodes[0].node.entropy_source, + &nodes[0].node.signer_provider, + nodes[1].node.get_our_node_id(), + &nodes[1].node.init_features(), + funding_satoshis, + initiator_funding_inputs.clone(), + 42, /* user_channel_id */ + nodes[0].node.get_current_default_configuration(), + nodes[0].best_block_info().1, + nodes[0].node.create_and_insert_outbound_scid_alias_for_test(), + ConfirmationTarget::NonAnchorChannelFee, + &logger_a, + ) + .unwrap(); + let open_channel_v2_msg = channel.get_open_channel_v2(nodes[0].chain_source.chain_hash); + + nodes[1].node.handle_open_channel_v2(nodes[0].node.get_our_node_id(), &open_channel_v2_msg); + + let accept_channel_v2_msg = get_event_msg!( + nodes[1], + MessageSendEvent::SendAcceptChannelV2, + nodes[0].node.get_our_node_id() + ); + let channel_id = ChannelId::v2_from_revocation_basepoints( + &RevocationBasepoint::from(accept_channel_v2_msg.common_fields.revocation_basepoint), + &RevocationBasepoint::from(open_channel_v2_msg.common_fields.revocation_basepoint), + ); + + let tx_add_input_msg = TxAddInput { + channel_id, + serial_id: 2, // Even serial_id from initiator. + prevtx: initiator_funding_inputs[0].1.clone(), + prevtx_out: 0, + sequence: initiator_funding_inputs[0].0.sequence.0, + shared_input_txid: None, + }; + let input_value = + tx_add_input_msg.prevtx.as_transaction().output[tx_add_input_msg.prevtx_out as usize].value; + assert_eq!(input_value.to_sat(), session.initiator_input_value_satoshis); + + nodes[1].node.handle_tx_add_input(nodes[0].node.get_our_node_id(), &tx_add_input_msg); + + let _tx_complete_msg = + get_event_msg!(nodes[1], MessageSendEvent::SendTxComplete, nodes[0].node.get_our_node_id()); + + let tx_add_output_msg = TxAddOutput { + channel_id, + serial_id: 4, + sats: funding_satoshis, + script: make_funding_redeemscript( + &open_channel_v2_msg.common_fields.funding_pubkey, + &accept_channel_v2_msg.common_fields.funding_pubkey, + ) + .to_p2wsh(), + }; + nodes[1].node.handle_tx_add_output(nodes[0].node.get_our_node_id(), &tx_add_output_msg); + + let _tx_complete_msg = + get_event_msg!(nodes[1], MessageSendEvent::SendTxComplete, nodes[0].node.get_our_node_id()); + + let tx_complete_msg = TxComplete { channel_id }; + + nodes[1].node.handle_tx_complete(nodes[0].node.get_our_node_id(), &tx_complete_msg); + let msg_events = nodes[1].node.get_and_clear_pending_msg_events(); + assert_eq!(msg_events.len(), 1); + let _msg_commitment_signed_from_1 = match msg_events[0] { + MessageSendEvent::UpdateHTLCs { ref node_id, ref updates } => { + assert_eq!(*node_id, nodes[0].node.get_our_node_id()); + updates.commitment_signed.clone() + }, + _ => panic!("Unexpected event"), + }; + + let (funding_outpoint, channel_type_features) = { + let per_peer_state = nodes[1].node.per_peer_state.read().unwrap(); + let peer_state = + per_peer_state.get(&nodes[0].node.get_our_node_id()).unwrap().lock().unwrap(); + let channel_context = + peer_state.channel_by_id.get(&tx_complete_msg.channel_id).unwrap().context(); + (channel_context.get_funding_txo(), channel_context.get_channel_type().clone()) + }; + + let channel_transaction_parameters = ChannelTransactionParameters { + counterparty_parameters: Some(CounterpartyChannelTransactionParameters { + pubkeys: ChannelPublicKeys { + funding_pubkey: accept_channel_v2_msg.common_fields.funding_pubkey, + revocation_basepoint: RevocationBasepoint( + accept_channel_v2_msg.common_fields.revocation_basepoint, + ), + payment_point: accept_channel_v2_msg.common_fields.payment_basepoint, + delayed_payment_basepoint: DelayedPaymentBasepoint( + accept_channel_v2_msg.common_fields.delayed_payment_basepoint, + ), + htlc_basepoint: HtlcBasepoint(accept_channel_v2_msg.common_fields.htlc_basepoint), + }, + selected_contest_delay: accept_channel_v2_msg.common_fields.to_self_delay, + }), + holder_pubkeys: ChannelPublicKeys { + funding_pubkey: open_channel_v2_msg.common_fields.funding_pubkey, + revocation_basepoint: RevocationBasepoint( + open_channel_v2_msg.common_fields.revocation_basepoint, + ), + payment_point: open_channel_v2_msg.common_fields.payment_basepoint, + delayed_payment_basepoint: DelayedPaymentBasepoint( + open_channel_v2_msg.common_fields.delayed_payment_basepoint, + ), + htlc_basepoint: HtlcBasepoint(open_channel_v2_msg.common_fields.htlc_basepoint), + }, + holder_selected_contest_delay: open_channel_v2_msg.common_fields.to_self_delay, + is_outbound_from_holder: true, + funding_outpoint, + channel_type_features, + }; + + channel + .context + .get_mut_signer() + .as_mut_ecdsa() + .unwrap() + .provide_channel_parameters(&channel_transaction_parameters); + + let msg_commitment_signed_from_0 = CommitmentSigned { + channel_id, + signature: channel + .context + .get_initial_counterparty_commitment_signature_for_test( + &&logger_a, + channel_transaction_parameters, + accept_channel_v2_msg.common_fields.first_per_commitment_point, + ) + .unwrap(), + htlc_signatures: vec![], + batch: None, + #[cfg(taproot)] + partial_signature_with_nonce: None, + }; + + if test_async_persist { + chanmon_cfgs[1] + .persister + .set_update_ret(crate::chain::ChannelMonitorUpdateStatus::InProgress); + } + + // Handle the initial commitment_signed exchange. Order is not important here. + nodes[1] + .node + .handle_commitment_signed(nodes[0].node.get_our_node_id(), &msg_commitment_signed_from_0); + check_added_monitors(&nodes[1], 1); + + if test_async_persist { + let events = nodes[1].node.get_and_clear_pending_events(); + assert!(events.is_empty()); + + chanmon_cfgs[1] + .persister + .set_update_ret(crate::chain::ChannelMonitorUpdateStatus::Completed); + let (outpoint, latest_update, _) = *nodes[1] + .chain_monitor + .latest_monitor_update_id + .lock() + .unwrap() + .get(&channel_id) + .unwrap(); + nodes[1].chain_monitor.chain_monitor.force_channel_monitor_updated(outpoint, latest_update); + } + + let events = nodes[1].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + match events[0] { + Event::ChannelPending { channel_id: chan_id, .. } => assert_eq!(chan_id, channel_id), + _ => panic!("Unexpected event"), + }; + + let tx_signatures_msg = get_event_msg!( + nodes[1], + MessageSendEvent::SendTxSignatures, + nodes[0].node.get_our_node_id() + ); + + assert_eq!(tx_signatures_msg.channel_id, channel_id); +} + +#[test] +fn test_v2_channel_establishment() { + // Only initiator contributes, no persist pending + do_test_v2_channel_establishment( + V2ChannelEstablishmentTestSession { initiator_input_value_satoshis: 100_000 }, + false, + ); + // Only initiator contributes, persist pending + do_test_v2_channel_establishment( + V2ChannelEstablishmentTestSession { initiator_input_value_satoshis: 100_000 }, + true, + ); +} diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index a6e07a8bc44..6c6b24bce7c 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -38,17 +38,20 @@ use crate::util::test_utils; use crate::util::test_utils::{TestChainMonitor, TestScorer, TestKeysInterface}; use crate::util::ser::{ReadableArgs, Writeable}; +use bitcoin::WPubkeyHash; use bitcoin::amount::Amount; -use bitcoin::block::{Block, Header, Version}; -use bitcoin::locktime::absolute::LockTime; -use bitcoin::transaction::{Transaction, TxIn, TxOut}; +use bitcoin::block::{Block, Header, Version as BlockVersion}; +use bitcoin::locktime::absolute::{LockTime, LOCK_TIME_THRESHOLD}; +use bitcoin::transaction::{Sequence, Transaction, TxIn, TxOut}; use bitcoin::hash_types::{BlockHash, TxMerkleNode}; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::Hash as _; use bitcoin::network::Network; use bitcoin::pow::CompactTarget; +use bitcoin::script::ScriptBuf; use bitcoin::secp256k1::{PublicKey, SecretKey}; -use bitcoin::transaction; +use bitcoin::transaction::{self, Version as TxVersion}; +use bitcoin::witness::Witness; use alloc::rc::Rc; use core::cell::RefCell; @@ -90,7 +93,7 @@ pub fn mine_transaction_without_consistency_checks<'a, 'b, 'c, 'd>(node: &'a Nod let height = node.best_block_info().1 + 1; let mut block = Block { header: Header { - version: Version::NO_SOFT_FORK_SIGNALLING, + version: BlockVersion::NO_SOFT_FORK_SIGNALLING, prev_blockhash: node.best_block_hash(), merkle_root: TxMerkleNode::all_zeros(), time: height, @@ -217,7 +220,7 @@ impl ConnectStyle { pub fn create_dummy_header(prev_blockhash: BlockHash, time: u32) -> Header { Header { - version: Version::NO_SOFT_FORK_SIGNALLING, + version: BlockVersion::NO_SOFT_FORK_SIGNALLING, prev_blockhash, merkle_root: TxMerkleNode::all_zeros(), time, @@ -1235,6 +1238,37 @@ fn internal_create_funding_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, } } +pub fn create_dual_funding_utxos_with_prev_txs( + node: &Node<'_, '_, '_>, utxo_values_in_satoshis: &[u64], +) -> Vec<(TxIn, Transaction)> { + // Ensure we have unique transactions per node by using the locktime. + let tx = Transaction { + version: TxVersion::TWO, + lock_time: LockTime::from_height( + u32::from_be_bytes(node.keys_manager.get_secure_random_bytes()[0..4].try_into().unwrap()) % LOCK_TIME_THRESHOLD + ).unwrap(), + input: vec![], + output: utxo_values_in_satoshis.iter().map(|value_satoshis| TxOut { + value: Amount::from_sat(*value_satoshis), script_pubkey: ScriptBuf::new_p2wpkh(&WPubkeyHash::all_zeros()), + }).collect() + }; + + let mut result = vec![]; + for i in 0..utxo_values_in_satoshis.len() { + result.push( + (TxIn { + previous_output: OutPoint { + txid: tx.compute_txid(), + index: i as u16, + }.into_bitcoin_outpoint(), + script_sig: ScriptBuf::new(), + sequence: Sequence::ZERO, + witness: Witness::new(), + }, tx.clone())); + } + result +} + pub fn sign_funding_transaction<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, node_b: &Node<'a, 'b, 'c>, channel_value: u64, expected_temporary_channel_id: ChannelId) -> Transaction { let (temporary_channel_id, tx, funding_output) = create_funding_transaction(node_a, &node_b.node.get_our_node_id(), channel_value, 42); assert_eq!(temporary_channel_id, expected_temporary_channel_id); diff --git a/lightning/src/ln/interactivetxs.rs b/lightning/src/ln/interactivetxs.rs index 15e911cbb4c..2b72133ec09 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::{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, } } @@ -165,23 +230,191 @@ impl ConstructedTransaction { .unwrap_or(Weight::MAX) } - 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); + pub fn build_unsigned_tx(&self) -> Transaction { + let ConstructedTransaction { inputs, outputs, .. } = self; - let input: Vec = inputs.into_iter().map(|input| input.txin().clone()).collect(); - let output: Vec = - outputs.into_iter().map(|output| output.tx_out().clone()).collect(); + let input: Vec = inputs.iter().map(|input| input.txin().clone()).collect(); + let output: Vec = outputs.iter().map(|output| output.tx_out().clone()).collect(); 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 compute_txid(&self) -> Txid { + self.build_unsigned_tx().compute_txid() + } + + /// Adds provided holder witnesses to holder inputs of unsigned transaction. + /// + /// Note that it is assumed that the witness count equals the holder input count. + 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); + } + + /// Adds counterparty witnesses to counterparty inputs of unsigned transaction. + /// + /// Note that it is assumed that the witness count equals the counterparty input count. + 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); + } +} + +/// The InteractiveTxSigningSession coordinates the signing flow of interactively constructed +/// transactions from exhange of `commitment_signed` to ensuring proper ordering of `tx_signature` +/// message exchange. +/// +/// See the specification for more details: +/// https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-commitment_signed-message +/// https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#sharing-funding-signatures-tx_signatures +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct InteractiveTxSigningSession { + pub unsigned_tx: ConstructedTransaction, + holder_sends_tx_signatures_first: bool, + received_commitment_signed: bool, + holder_tx_signatures: Option, + counterparty_sent_tx_signatures: bool, +} + +impl InteractiveTxSigningSession { + pub fn received_commitment_signed(&mut self) -> Option { + self.received_commitment_signed = true; + if self.holder_sends_tx_signatures_first { + self.holder_tx_signatures.clone() + } else { + None + } + } + + pub fn get_tx_signatures(&self) -> Option { + if self.received_commitment_signed { + self.holder_tx_signatures.clone() + } else { + None + } + } + + /// Handles a `tx_signatures` message received from the counterparty. + /// + /// Returns an error if the witness count does not equal the counterparty's input count in the + /// unsigned transaction. + pub fn received_tx_signatures( + &mut self, tx_signatures: TxSignatures, + ) -> Result<(Option, Option), ()> { + if self.counterparty_sent_tx_signatures { + return Ok((None, None)); + }; + if self.remote_inputs_count() != tx_signatures.witnesses.len() { + return Err(()); + } + self.unsigned_tx.add_remote_witnesses(tx_signatures.witnesses.clone()); + self.counterparty_sent_tx_signatures = true; + + 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 + }; + + Ok((holder_tx_signatures, funding_tx)) + } + + /// Provides the holder witnesses for the unsigned transaction. + /// + /// Returns an error if the witness count does not equal the holder's input count in the + /// unsigned transaction. + pub fn provide_holder_witnesses( + &mut self, channel_id: ChannelId, witnesses: Vec, + ) -> Result, ()> { + if self.local_inputs_count() != witnesses.len() { + return Err(()); + } + + self.unsigned_tx.add_local_witnesses(witnesses.clone()); + self.holder_tx_signatures = Some(TxSignatures { + channel_id, + tx_hash: self.unsigned_tx.compute_txid(), + witnesses: witnesses.into_iter().collect(), + shared_input_signature: None, + }); + if self.received_commitment_signed + && (self.holder_sends_tx_signatures_first || self.counterparty_sent_tx_signatures) + { + Ok(self.holder_tx_signatures.clone()) + } else { + Ok(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 +460,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 +505,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 +538,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 +940,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 +983,14 @@ 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, + received_commitment_signed: false, + holder_tx_signatures: None, + counterparty_sent_tx_signatures: false, + }; + Ok(NegotiationComplete(signing_session)) } } @@ -810,10 +1059,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 +1144,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 @@ -941,6 +1193,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 +1238,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 +1285,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 +1325,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 +1340,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) => {{ @@ -1088,10 +1399,65 @@ where serial_id } -pub(crate) enum HandleTxCompleteValue { +pub(super) enum HandleTxCompleteValue { SendTxMessage(InteractiveTxMessageSend), - SendTxComplete(InteractiveTxMessageSend, ConstructedTransaction), - NegotiationComplete(ConstructedTransaction), + SendTxComplete(InteractiveTxMessageSend, InteractiveTxSigningSession), + NegotiationComplete(InteractiveTxSigningSession), +} + +impl HandleTxCompleteValue { + pub fn into_msg_send_event_or_signing_session( + self, counterparty_node_id: PublicKey, + ) -> (Option, Option) { + match self { + HandleTxCompleteValue::SendTxMessage(msg) => { + (Some(msg.into_msg_send_event(counterparty_node_id)), None) + }, + HandleTxCompleteValue::SendTxComplete(msg, signing_session) => { + (Some(msg.into_msg_send_event(counterparty_node_id)), Some(signing_session)) + }, + HandleTxCompleteValue::NegotiationComplete(signing_session) => { + (None, Some(signing_session)) + }, + } + } +} + +pub(super) struct HandleTxCompleteResult(pub Result); + +impl HandleTxCompleteResult { + pub fn into_msg_send_event_or_signing_session( + self, counterparty_node_id: PublicKey, + ) -> (Option, Option) { + match self.0 { + Ok(interactive_tx_msg_send) => { + interactive_tx_msg_send.into_msg_send_event_or_signing_session(counterparty_node_id) + }, + Err(tx_abort_msg) => ( + Some(MessageSendEvent::SendTxAbort { + node_id: counterparty_node_id, + msg: tx_abort_msg, + }), + None, + ), + } + } +} + +pub(super) struct InteractiveTxConstructorArgs<'a, ES: Deref> +where + ES::Target: EntropySource, +{ + 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 +1469,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 +1521,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 +1549,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 +1668,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 +1681,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 +1768,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 +1829,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 +1852,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 +1899,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.compute_txid()); }, Err(abort_reason) => { let error_culprit = match abort_reason { @@ -1546,9 +1931,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.compute_txid()); }, Err(abort_reason) => { let error_culprit = match abort_reason { @@ -1571,7 +1957,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 9a9689b3898..e1631a2892c 100644 --- a/lightning/src/ln/mod.rs +++ b/lightning/src/ln/mod.rs @@ -44,6 +44,9 @@ pub(crate) mod onion_utils; mod outbound_payment; pub mod wire; +#[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled. +pub(crate) mod interactivetxs; + pub use onion_utils::create_payment_onion; // Older rustc (which we support) refuses to let us call the get_payment_preimage_hash!() macro // without the node parameter being mut. This is incorrect, and thus newer rustcs will complain @@ -88,7 +91,8 @@ 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; +#[cfg(test)] +#[allow(unused_mut)] +mod dual_funding_tests; 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 6f508efa636..c20d2027209 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -183,10 +183,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 @@ -288,11 +288,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 @@ -307,10 +307,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. @@ -370,11 +370,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 @@ -504,9 +504,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 @@ -525,9 +525,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 @@ -541,9 +541,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 @@ -552,9 +552,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 @@ -563,20 +563,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 @@ -589,10 +589,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 @@ -606,10 +606,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 @@ -619,9 +619,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 96a5df3b464..3bed6a0894a 100644 --- a/lightning/src/ln/peer_handler.rs +++ b/lightning/src/ln/peer_handler.rs @@ -340,6 +340,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(); diff --git a/pending_changelog/3137-accept-dual-funding-without-contributing.txt b/pending_changelog/3137-accept-dual-funding-without-contributing.txt new file mode 100644 index 00000000000..486e71a46b2 --- /dev/null +++ b/pending_changelog/3137-accept-dual-funding-without-contributing.txt @@ -0,0 +1,15 @@ +# API Updates + * Accepting dual-funded (V2 establishment) channels (without contibuting) is now supported (#3137). + Some particulars to be aware of for this feature: + * Creating dual-funded channels is not yet supported. + * Contributing funds (inputs) to accepted channels is not yet supported. + * `Event::OpenChannelRequest::push_msat` has been replaced by the field `channel_negotiation_type` to + differentiate between an inbound request for a dual-funded (V2) or non-dual-funded (V1) channel to be + opened, with value being either of the enum variants `InboundChannelFunds::DualFunded` and + `InboundChannelFunds::PushMsat(u64)` corresponding to V2 and V1 channel open requests respectively. + * If `manually_accept_inbound_channels` is false, then V2 channels will be accepted automatically; the + same behaviour as V1 channels. Otherwise, `ChannelManager::accept_inbound_channel()` can also be used + to manually accept an inbound V2 channel. + * 0conf dual-funded channels are not supported. + * RBF of dual-funded channel funding transactions is not supported. +