diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index a8d055a9c5b..91e9a6dda84 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -46,8 +46,8 @@ use crate::chain::{BestBlock, WatchedOutput}; use crate::events::bump_transaction::{AnchorDescriptor, BumpTransactionEvent}; use crate::events::{ClosureReason, Event, EventHandler, ReplayEvent}; use crate::ln::chan_utils::{ - self, ChannelTransactionParameters, CommitmentTransaction, CounterpartyCommitmentSecrets, - HTLCClaim, HTLCOutputInCommitment, HolderCommitmentTransaction, + self, ChannelTransactionParameters, ChannelTransactionParametersAccess, CommitmentTransaction, + CounterpartyCommitmentSecrets, HTLCClaim, HTLCOutputInCommitment, HolderCommitmentTransaction, }; use crate::ln::channel::INITIAL_COMMITMENT_NUMBER; use crate::ln::channel_keys::{ @@ -118,9 +118,7 @@ impl ChannelMonitorUpdate { ) -> impl Iterator + '_ { self.updates.iter().filter_map(|update| match update { ChannelMonitorUpdateStep::RenegotiatedFunding { channel_parameters, .. } => { - let funding_outpoint = channel_parameters - .funding_outpoint - .expect("Renegotiated funding must always have known outpoint"); + let funding_outpoint = channel_parameters.funding_outpoint; let funding_script = channel_parameters.make_funding_redeemscript().to_p2wsh(); Some((funding_outpoint, funding_script)) }, @@ -1170,8 +1168,7 @@ struct FundingScope { impl FundingScope { fn funding_outpoint(&self) -> OutPoint { - let funding_outpoint = self.channel_parameters.funding_outpoint.as_ref(); - *funding_outpoint.expect("Funding outpoint must be set for active monitor") + self.channel_parameters.funding_outpoint } fn funding_txid(&self) -> Txid { @@ -1868,7 +1865,7 @@ impl ChannelMonitor { &channel_parameters.channel_type_features, &holder_pubkeys.payment_point ); - let counterparty_channel_parameters = channel_parameters.counterparty_parameters.as_ref().unwrap(); + let counterparty_channel_parameters = &channel_parameters.counterparty_parameters; let counterparty_delayed_payment_base_key = counterparty_channel_parameters.pubkeys.delayed_payment_basepoint; let counterparty_htlc_base_key = counterparty_channel_parameters.pubkeys.htlc_basepoint; let counterparty_commitment_params = CounterpartyCommitmentParameters { counterparty_delayed_payment_base_key, counterparty_htlc_base_key, on_counterparty_tx_csv }; @@ -1885,8 +1882,7 @@ impl ChannelMonitor { initial_holder_commitment_tx.clone(), secp_ctx, ); - let funding_outpoint = channel_parameters.funding_outpoint - .expect("Funding outpoint must be known during initialization"); + let funding_outpoint = channel_parameters.funding_outpoint; let funding_redeem_script = channel_parameters.make_funding_redeemscript(); let funding_script = funding_redeem_script.to_p2wsh(); let mut outputs_to_watch = new_hash_map(); @@ -4231,7 +4227,7 @@ impl ChannelMonitorImpl { channel_parameters, holder_commitment_tx, counterparty_commitment_tx, } => { log_trace!(logger, "Updating ChannelMonitor with alternative holder and counterparty commitment transactions for funding txid {}", - channel_parameters.funding_outpoint.unwrap().txid); + channel_parameters.funding_outpoint.txid); if let Err(_) = self.renegotiated_funding( logger, channel_parameters, holder_commitment_tx, counterparty_commitment_tx, ) { @@ -4345,7 +4341,6 @@ impl ChannelMonitorImpl { #[rustfmt::skip] fn get_funding_txo(&self) -> OutPoint { self.funding.channel_parameters.funding_outpoint - .expect("Funding outpoint must be set for active monitor") } /// Returns the P2WSH script we are currently monitoring the chain for spends. This will change @@ -6981,11 +6976,11 @@ mod tests { holder_pubkeys: keys.pubkeys(&secp_ctx), holder_selected_contest_delay: 66, is_outbound_from_holder: true, - counterparty_parameters: Some(CounterpartyChannelTransactionParameters { + counterparty_parameters: CounterpartyChannelTransactionParameters { pubkeys: counterparty_pubkeys, selected_contest_delay: 67, - }), - funding_outpoint: Some(funding_outpoint), + }, + funding_outpoint, splice_parent_funding_txid: None, channel_type_features: ChannelTypeFeatures::only_static_remote_key(), channel_value_satoshis: 0, @@ -7244,11 +7239,11 @@ mod tests { holder_pubkeys: keys.pubkeys(&secp_ctx), holder_selected_contest_delay: 66, is_outbound_from_holder: true, - counterparty_parameters: Some(CounterpartyChannelTransactionParameters { + counterparty_parameters: CounterpartyChannelTransactionParameters { pubkeys: counterparty_pubkeys, selected_contest_delay: 67, - }), - funding_outpoint: Some(funding_outpoint), + }, + funding_outpoint, splice_parent_funding_txid: None, channel_type_features: ChannelTypeFeatures::only_static_remote_key(), channel_value_satoshis: 0, diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs index 3eb6d64f3a2..512955fd16d 100644 --- a/lightning/src/chain/onchaintx.rs +++ b/lightning/src/chain/onchaintx.rs @@ -1354,11 +1354,11 @@ mod tests { holder_pubkeys: signer.pubkeys(&secp_ctx), holder_selected_contest_delay: 66, is_outbound_from_holder: true, - counterparty_parameters: Some(CounterpartyChannelTransactionParameters { + counterparty_parameters: CounterpartyChannelTransactionParameters { pubkeys: counterparty_pubkeys, selected_contest_delay: 67, - }), - funding_outpoint: Some(funding_outpoint), + }, + funding_outpoint, splice_parent_funding_txid: None, channel_type_features: ChannelTypeFeatures::only_static_remote_key(), channel_value_satoshis: 0, diff --git a/lightning/src/chain/package.rs b/lightning/src/chain/package.rs index 0ef8855242b..773e6871cd8 100644 --- a/lightning/src/chain/package.rs +++ b/lightning/src/chain/package.rs @@ -31,8 +31,8 @@ use crate::chain::channelmonitor::COUNTERPARTY_CLAIMABLE_WITHIN_BLOCKS_PINNABLE; use crate::chain::onchaintx::{FeerateStrategy, OnchainTxHandler}; use crate::chain::transaction::MaybeSignedTransaction; use crate::ln::chan_utils::{ - self, ChannelTransactionParameters, HTLCOutputInCommitment, HolderCommitmentTransaction, - TxCreationKeys, + self, ChannelTransactionParameters, ChannelTransactionParametersAccess, HTLCOutputInCommitment, + HolderCommitmentTransaction, TxCreationKeys, }; use crate::ln::channel_keys::{DelayedPaymentBasepoint, HtlcBasepoint}; use crate::ln::channelmanager::MIN_CLTV_EXPIRY_DELTA; @@ -1892,7 +1892,7 @@ mod tests { payment_hash: PaymentHash::from(preimage), transaction_output_index: None, }; - let funding_outpoint = channel_parameters.funding_outpoint.unwrap(); + let funding_outpoint = channel_parameters.funding_outpoint; let commitment_tx = HolderCommitmentTransaction::dummy(0, funding_outpoint, vec![htlc.clone()]); let trusted_tx = commitment_tx.trust(); PackageSolvingData::HolderHTLCOutput(HolderHTLCOutput::build( @@ -1929,7 +1929,7 @@ mod tests { payment_hash: PaymentHash::from(PaymentPreimage([2;32])), transaction_output_index: None, }; - let funding_outpoint = channel_parameters.funding_outpoint.unwrap(); + let funding_outpoint = channel_parameters.funding_outpoint; let commitment_tx = HolderCommitmentTransaction::dummy(0, funding_outpoint, vec![htlc.clone()]); let trusted_tx = commitment_tx.trust(); PackageSolvingData::HolderHTLCOutput(HolderHTLCOutput::build( @@ -1957,7 +1957,7 @@ mod tests { macro_rules! dumb_funding_output { () => {{ let mut channel_parameters = ChannelTransactionParameters::test_dummy(0); - let funding_outpoint = channel_parameters.funding_outpoint.unwrap(); + let funding_outpoint = channel_parameters.funding_outpoint; let commitment_tx = HolderCommitmentTransaction::dummy(0, funding_outpoint, Vec::new()); channel_parameters.channel_type_features = ChannelTypeFeatures::only_static_remote_key(); PackageSolvingData::HolderFundingOutput(HolderFundingOutput::build( diff --git a/lightning/src/ln/async_signer_tests.rs b/lightning/src/ln/async_signer_tests.rs index 451af3918bf..3f115328e0d 100644 --- a/lightning/src/ln/async_signer_tests.rs +++ b/lightning/src/ln/async_signer_tests.rs @@ -1244,12 +1244,11 @@ fn do_test_closing_signed(extra_closing_signed: bool, reconnect: bool) { let per_peer_state = nodes[1].node.per_peer_state.read().unwrap(); let mut chan_lock = per_peer_state.get(&node_a_id).unwrap().lock().unwrap(); let channel = chan_lock.channel_by_id.get_mut(&chan_id).unwrap(); - let (funding, context) = channel.funding_and_context_mut(); - - let signer = context.get_mut_signer().as_mut_ecdsa().unwrap(); + let funded = channel.as_funded_mut().unwrap(); + let signer = funded.context.get_mut_signer().as_mut_ecdsa().unwrap(); let signature = signer .sign_closing_transaction( - &funding.channel_transaction_parameters, + &funded.funding.channel_transaction_parameters, &closing_tx_2, &Secp256k1::new(), ) diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index 4bb8ffac9ef..66e4f462b82 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -1040,13 +1040,49 @@ pub fn build_keyed_anchor_input_witness( ret } -/// Per-channel data used to build transactions in conjunction with the per-commitment data (CommitmentTransaction). -/// The fields are organized by holder/counterparty. +/// Trait providing unified access to channel transaction parameters fields shared by both +/// [`PartialChannelTransactionParameters`] and [`ChannelTransactionParameters`]. /// -/// Normally, this is converted to the broadcaster/countersignatory-organized DirectedChannelTransactionParameters -/// before use, via the as_holder_broadcastable and as_counterparty_broadcastable functions. +/// This enables generic code (e.g., `FundingScope

`) to access common fields without +/// knowing which concrete parameters type is in use. +pub(crate) trait ChannelTransactionParametersAccess { + fn holder_pubkeys(&self) -> &ChannelPublicKeys; + fn holder_selected_contest_delay(&self) -> u16; + fn is_outbound_from_holder(&self) -> bool; + fn channel_type_features(&self) -> &ChannelTypeFeatures; + fn channel_value_satoshis(&self) -> u64; + fn splice_parent_funding_txid(&self) -> Option; + /// Returns the funding outpoint, if known. + fn funding_outpoint(&self) -> Option; + /// Returns the counterparty parameters, if known. + fn counterparty_parameters(&self) -> Option<&CounterpartyChannelTransactionParameters>; + + /// Builds the funding redeemscript, if counterparty parameters are known. + fn make_funding_redeemscript_opt(&self) -> Option { + self.counterparty_parameters().map(|p| { + make_funding_redeemscript( + &self.holder_pubkeys().funding_pubkey, + &p.pubkeys.funding_pubkey, + ) + }) + } + + /// Builds the funding redeemscript, panicking if counterparty parameters are not yet known. + fn make_funding_redeemscript(&self) -> ScriptBuf { + self.make_funding_redeemscript_opt().unwrap() + } +} + +/// Per-channel data used to build transactions in conjunction with the per-commitment data +/// (CommitmentTransaction). The fields are organized by holder/counterparty. +/// +/// This is the internal variant used during channel negotiation, where counterparty parameters and +/// the funding outpoint may not yet be known. Once both are populated, this can be converted to +/// [`ChannelTransactionParameters`] via [`into_complete`]. +/// +/// [`into_complete`]: PartialChannelTransactionParameters::into_complete #[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub struct ChannelTransactionParameters { +pub(crate) struct PartialChannelTransactionParameters { /// Holder public keys pub holder_pubkeys: ChannelPublicKeys, /// The contest delay selected by the holder, which applies to counterparty-broadcast transactions @@ -1060,6 +1096,36 @@ pub struct ChannelTransactionParameters { /// The late-bound funding outpoint pub funding_outpoint: Option, /// The parent funding txid for a channel that has been spliced. + pub splice_parent_funding_txid: Option, + /// This channel's type, as negotiated during channel open. + pub channel_type_features: ChannelTypeFeatures, + /// The value locked in the channel, denominated in satoshis. + pub channel_value_satoshis: u64, +} + +/// Per-channel data used to build transactions in conjunction with the per-commitment data (CommitmentTransaction). +/// The fields are organized by holder/counterparty. +/// +/// Normally, this is converted to the broadcaster/countersignatory-organized DirectedChannelTransactionParameters +/// before use, via the as_holder_broadcastable and as_counterparty_broadcastable functions. +/// +/// All late-bound fields (counterparty parameters and funding outpoint) are guaranteed to be +/// present. For the partially-populated variant used during channel negotiation, see +/// [`PartialChannelTransactionParameters`]. +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct ChannelTransactionParameters { + /// Holder public keys + pub holder_pubkeys: ChannelPublicKeys, + /// The contest delay selected by the holder, which applies to counterparty-broadcast transactions + pub holder_selected_contest_delay: u16, + /// Whether the holder is the initiator of this channel. + /// This is an input to the commitment number obscure factor computation. + pub is_outbound_from_holder: bool, + /// The counterparty channel transaction parameters. + pub counterparty_parameters: CounterpartyChannelTransactionParameters, + /// The funding outpoint + pub funding_outpoint: chain::transaction::OutPoint, + /// The parent funding txid for a channel that has been spliced. /// /// If a channel was funded with transaction A, and later spliced with transaction B, this field /// tracks the txid of transaction A. @@ -1086,54 +1152,119 @@ pub struct CounterpartyChannelTransactionParameters { pub selected_contest_delay: u16, } -impl ChannelTransactionParameters { - /// Whether the late bound parameters are populated. - pub fn is_populated(&self) -> bool { - self.counterparty_parameters.is_some() && self.funding_outpoint.is_some() +impl ChannelTransactionParametersAccess for PartialChannelTransactionParameters { + fn holder_pubkeys(&self) -> &ChannelPublicKeys { + &self.holder_pubkeys + } + fn holder_selected_contest_delay(&self) -> u16 { + self.holder_selected_contest_delay + } + fn is_outbound_from_holder(&self) -> bool { + self.is_outbound_from_holder + } + fn channel_type_features(&self) -> &ChannelTypeFeatures { + &self.channel_type_features + } + fn channel_value_satoshis(&self) -> u64 { + self.channel_value_satoshis + } + fn splice_parent_funding_txid(&self) -> Option { + self.splice_parent_funding_txid } + fn funding_outpoint(&self) -> Option { + self.funding_outpoint + } + fn counterparty_parameters(&self) -> Option<&CounterpartyChannelTransactionParameters> { + self.counterparty_parameters.as_ref() + } +} +impl ChannelTransactionParametersAccess for ChannelTransactionParameters { + fn holder_pubkeys(&self) -> &ChannelPublicKeys { + &self.holder_pubkeys + } + fn holder_selected_contest_delay(&self) -> u16 { + self.holder_selected_contest_delay + } + fn is_outbound_from_holder(&self) -> bool { + self.is_outbound_from_holder + } + fn channel_type_features(&self) -> &ChannelTypeFeatures { + &self.channel_type_features + } + fn channel_value_satoshis(&self) -> u64 { + self.channel_value_satoshis + } + fn splice_parent_funding_txid(&self) -> Option { + self.splice_parent_funding_txid + } + fn funding_outpoint(&self) -> Option { + Some(self.funding_outpoint) + } + fn counterparty_parameters(&self) -> Option<&CounterpartyChannelTransactionParameters> { + Some(&self.counterparty_parameters) + } +} + +impl PartialChannelTransactionParameters { + /// Converts to the complete type, returning `None` if late-bound fields are not populated. + pub(crate) fn into_complete(self) -> Option { + Some(ChannelTransactionParameters { + holder_pubkeys: self.holder_pubkeys, + holder_selected_contest_delay: self.holder_selected_contest_delay, + is_outbound_from_holder: self.is_outbound_from_holder, + counterparty_parameters: self.counterparty_parameters?, + funding_outpoint: self.funding_outpoint?, + splice_parent_funding_txid: self.splice_parent_funding_txid, + channel_type_features: self.channel_type_features, + channel_value_satoshis: self.channel_value_satoshis, + }) + } +} + +impl ChannelTransactionParameters { /// Convert the holder/counterparty parameters to broadcaster/countersignatory-organized parameters, /// given that the holder is the broadcaster. - /// - /// self.is_populated() must be true before calling this function. #[rustfmt::skip] pub fn as_holder_broadcastable(&self) -> DirectedChannelTransactionParameters<'_> { - assert!(self.is_populated(), "self.late_parameters must be set before using as_holder_broadcastable"); DirectedChannelTransactionParameters { - inner: self, - holder_is_broadcaster: true + broadcaster_pubkeys: &self.holder_pubkeys, + countersignatory_pubkeys: &self.counterparty_parameters.pubkeys, + contest_delay: self.counterparty_parameters.selected_contest_delay, + is_outbound: self.is_outbound_from_holder, + funding_outpoint: self.funding_outpoint.into_bitcoin_outpoint(), + channel_type_features: &self.channel_type_features, + channel_value_satoshis: self.channel_value_satoshis, } } /// Convert the holder/counterparty parameters to broadcaster/countersignatory-organized parameters, /// given that the counterparty is the broadcaster. - /// - /// self.is_populated() must be true before calling this function. #[rustfmt::skip] pub fn as_counterparty_broadcastable(&self) -> DirectedChannelTransactionParameters<'_> { - assert!(self.is_populated(), "self.late_parameters must be set before using as_counterparty_broadcastable"); DirectedChannelTransactionParameters { - inner: self, - holder_is_broadcaster: false + broadcaster_pubkeys: &self.counterparty_parameters.pubkeys, + countersignatory_pubkeys: &self.holder_pubkeys, + contest_delay: self.holder_selected_contest_delay, + is_outbound: !self.is_outbound_from_holder, + funding_outpoint: self.funding_outpoint.into_bitcoin_outpoint(), + channel_type_features: &self.channel_type_features, + channel_value_satoshis: self.channel_value_satoshis, } } - pub(crate) fn make_funding_redeemscript(&self) -> ScriptBuf { - self.make_funding_redeemscript_opt().unwrap() - } - - pub(crate) fn make_funding_redeemscript_opt(&self) -> Option { - self.counterparty_parameters.as_ref().map(|p| { - make_funding_redeemscript( - &self.holder_pubkeys.funding_pubkey, - &p.pubkeys.funding_pubkey, - ) - }) - } - - /// Returns the counterparty's pubkeys. - pub fn counterparty_pubkeys(&self) -> Option<&ChannelPublicKeys> { - self.counterparty_parameters.as_ref().map(|params| ¶ms.pubkeys) + /// Converts to the partial type (with Options). + pub(crate) fn into_partial(self) -> PartialChannelTransactionParameters { + PartialChannelTransactionParameters { + holder_pubkeys: self.holder_pubkeys, + holder_selected_contest_delay: self.holder_selected_contest_delay, + is_outbound_from_holder: self.is_outbound_from_holder, + counterparty_parameters: Some(self.counterparty_parameters), + funding_outpoint: Some(self.funding_outpoint), + splice_parent_funding_txid: self.splice_parent_funding_txid, + channel_type_features: self.channel_type_features, + channel_value_satoshis: self.channel_value_satoshis, + } } #[cfg(test)] @@ -1150,13 +1281,13 @@ impl ChannelTransactionParameters { holder_pubkeys: dummy_keys.clone(), holder_selected_contest_delay: 42, is_outbound_from_holder: true, - counterparty_parameters: Some(CounterpartyChannelTransactionParameters { + counterparty_parameters: CounterpartyChannelTransactionParameters { pubkeys: dummy_keys, selected_contest_delay: 42, - }), - funding_outpoint: Some(chain::transaction::OutPoint { + }, + funding_outpoint: chain::transaction::OutPoint { txid: Txid::from_byte_array([42; 32]), index: 0 - }), + }, splice_parent_funding_txid: None, channel_type_features: ChannelTypeFeatures::empty(), channel_value_satoshis, @@ -1173,12 +1304,14 @@ impl Writeable for ChannelTransactionParameters { #[rustfmt::skip] fn write(&self, writer: &mut W) -> Result<(), io::Error> { let legacy_deserialization_prevention_marker = legacy_deserialization_prevention_marker_for_channel_type_features(&self.channel_type_features); + let counterparty_parameters = Some(&self.counterparty_parameters); + let funding_outpoint = Some(&self.funding_outpoint); write_tlv_fields!(writer, { (0, self.holder_pubkeys, required), (2, self.holder_selected_contest_delay, required), (4, self.is_outbound_from_holder, required), - (6, self.counterparty_parameters, option), - (8, self.funding_outpoint, option), + (6, counterparty_parameters, option), + (8, funding_outpoint, option), (10, legacy_deserialization_prevention_marker, option), (11, self.channel_type_features, required), (12, self.splice_parent_funding_txid, option), @@ -1189,6 +1322,13 @@ impl Writeable for ChannelTransactionParameters { } impl ReadableArgs> for ChannelTransactionParameters { + fn read(reader: &mut R, read_args: Option) -> Result { + let params = PartialChannelTransactionParameters::read(reader, read_args)?; + params.into_complete().ok_or(DecodeError::InvalidValue) + } +} + +impl ReadableArgs> for PartialChannelTransactionParameters { #[rustfmt::skip] fn read(reader: &mut R, read_args: Option) -> Result { let mut holder_pubkeys = RequiredWrapper(None); @@ -1248,61 +1388,60 @@ impl ReadableArgs> for ChannelTransactionParameters { /// This is derived from the holder/counterparty-organized ChannelTransactionParameters via the /// as_holder_broadcastable and as_counterparty_broadcastable functions. pub struct DirectedChannelTransactionParameters<'a> { - /// The holder's channel static parameters - inner: &'a ChannelTransactionParameters, - /// Whether the holder is the broadcaster - holder_is_broadcaster: bool, + /// The channel pubkeys for the broadcaster + broadcaster_pubkeys: &'a ChannelPublicKeys, + /// The channel pubkeys for the countersignatory + countersignatory_pubkeys: &'a ChannelPublicKeys, + /// The contest delay selected by the countersignatory + contest_delay: u16, + /// Whether the channel is outbound from the broadcaster + is_outbound: bool, + /// The funding outpoint + funding_outpoint: OutPoint, + /// The channel type features + channel_type_features: &'a ChannelTypeFeatures, + /// The value locked in the channel + channel_value_satoshis: u64, } impl<'a> DirectedChannelTransactionParameters<'a> { /// Get the channel pubkeys for the broadcaster pub fn broadcaster_pubkeys(&self) -> &'a ChannelPublicKeys { - if self.holder_is_broadcaster { - &self.inner.holder_pubkeys - } else { - &self.inner.counterparty_parameters.as_ref().unwrap().pubkeys - } + self.broadcaster_pubkeys } /// Get the channel pubkeys for the countersignatory pub fn countersignatory_pubkeys(&self) -> &'a ChannelPublicKeys { - if self.holder_is_broadcaster { - &self.inner.counterparty_parameters.as_ref().unwrap().pubkeys - } else { - &self.inner.holder_pubkeys - } + self.countersignatory_pubkeys } /// Get the contest delay applicable to the transactions. /// Note that the contest delay was selected by the countersignatory. - #[rustfmt::skip] pub fn contest_delay(&self) -> u16 { - let counterparty_parameters = self.inner.counterparty_parameters.as_ref().unwrap(); - if self.holder_is_broadcaster { counterparty_parameters.selected_contest_delay } else { self.inner.holder_selected_contest_delay } + self.contest_delay } /// Whether the channel is outbound from the broadcaster. /// /// The boolean representing the side that initiated the channel is /// an input to the commitment number obscure factor computation. - #[rustfmt::skip] pub fn is_outbound(&self) -> bool { - if self.holder_is_broadcaster { self.inner.is_outbound_from_holder } else { !self.inner.is_outbound_from_holder } + self.is_outbound } /// The funding outpoint pub fn funding_outpoint(&self) -> OutPoint { - self.inner.funding_outpoint.unwrap().into_bitcoin_outpoint() + self.funding_outpoint } /// The type of channel these parameters are for pub fn channel_type_features(&self) -> &'a ChannelTypeFeatures { - &self.inner.channel_type_features + self.channel_type_features } /// The value locked in the channel, denominated in satoshis. pub fn channel_value_satoshis(&self) -> u64 { - self.inner.channel_value_satoshis + self.channel_value_satoshis } } @@ -1362,8 +1501,8 @@ impl HolderCommitmentTransaction { holder_pubkeys: channel_pubkeys.clone(), holder_selected_contest_delay: 0, is_outbound_from_holder: false, - counterparty_parameters: Some(CounterpartyChannelTransactionParameters { pubkeys: channel_pubkeys.clone(), selected_contest_delay: 0 }), - funding_outpoint: Some(funding_outpoint), + counterparty_parameters: CounterpartyChannelTransactionParameters { pubkeys: channel_pubkeys.clone(), selected_contest_delay: 0 }, + funding_outpoint: funding_outpoint, splice_parent_funding_txid: None, channel_type_features: ChannelTypeFeatures::only_static_remote_key(), channel_value_satoshis, @@ -2299,8 +2438,8 @@ mod tests { holder_pubkeys: holder_pubkeys.clone(), holder_selected_contest_delay: 0, is_outbound_from_holder: false, - counterparty_parameters: Some(CounterpartyChannelTransactionParameters { pubkeys: counterparty_pubkeys.clone(), selected_contest_delay: 0 }), - funding_outpoint: Some(chain::transaction::OutPoint { txid: Txid::all_zeros(), index: 0 }), + counterparty_parameters: CounterpartyChannelTransactionParameters { pubkeys: counterparty_pubkeys.clone(), selected_contest_delay: 0 }, + funding_outpoint: chain::transaction::OutPoint { txid: Txid::all_zeros(), index: 0 }, splice_parent_funding_txid: None, channel_type_features: ChannelTypeFeatures::only_static_remote_key(), channel_value_satoshis: 4000, diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 9361cd3c749..657cf2229ea 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -41,8 +41,9 @@ use crate::ln::chan_utils; use crate::ln::chan_utils::{ get_commitment_transaction_number_obscure_factor, max_htlcs, second_stage_tx_fees_sat, selected_commitment_sat_per_1000_weight, ChannelPublicKeys, ChannelTransactionParameters, - ClosingTransaction, CommitmentTransaction, CounterpartyChannelTransactionParameters, - CounterpartyCommitmentSecrets, HTLCOutputInCommitment, HolderCommitmentTransaction, + ChannelTransactionParametersAccess, ClosingTransaction, CommitmentTransaction, + CounterpartyChannelTransactionParameters, CounterpartyCommitmentSecrets, + HTLCOutputInCommitment, HolderCommitmentTransaction, PartialChannelTransactionParameters, EMPTY_SCRIPT_SIG_WEIGHT, FUNDING_TRANSACTION_WITNESS_WEIGHT, }; use crate::ln::channel_state::{ @@ -1488,8 +1489,10 @@ pub(super) struct Channel { enum ChannelPhase { Undefined, UnfundedOutboundV1(OutboundV1Channel), + PendingV1(PendingV1Channel), UnfundedInboundV1(InboundV1Channel), - UnfundedV2(PendingV2Channel), + UnfundedV2(UnfundedV2Channel), + PendingV2(PendingV2Channel), Funded(FundedChannel), } @@ -1502,8 +1505,10 @@ where ChannelPhase::Undefined => unreachable!(), ChannelPhase::Funded(chan) => &chan.context, ChannelPhase::UnfundedOutboundV1(chan) => &chan.context, + ChannelPhase::PendingV1(chan) => &chan.context, ChannelPhase::UnfundedInboundV1(chan) => &chan.context, ChannelPhase::UnfundedV2(chan) => &chan.context, + ChannelPhase::PendingV2(chan) => &chan.context, } } @@ -1512,39 +1517,98 @@ where ChannelPhase::Undefined => unreachable!(), ChannelPhase::Funded(chan) => &mut chan.context, ChannelPhase::UnfundedOutboundV1(chan) => &mut chan.context, + ChannelPhase::PendingV1(chan) => &mut chan.context, ChannelPhase::UnfundedInboundV1(chan) => &mut chan.context, ChannelPhase::UnfundedV2(chan) => &mut chan.context, + ChannelPhase::PendingV2(chan) => &mut chan.context, } } - pub fn funding(&self) -> &FundingScope { + pub fn is_outbound(&self) -> bool { match &self.phase { ChannelPhase::Undefined => unreachable!(), - ChannelPhase::Funded(chan) => &chan.funding, - ChannelPhase::UnfundedOutboundV1(chan) => &chan.funding, - ChannelPhase::UnfundedInboundV1(chan) => &chan.funding, - ChannelPhase::UnfundedV2(chan) => &chan.funding, + ChannelPhase::Funded(chan) => chan.funding.is_outbound(), + ChannelPhase::UnfundedOutboundV1(chan) => chan.funding.is_outbound(), + ChannelPhase::PendingV1(chan) => chan.funding.is_outbound(), + ChannelPhase::UnfundedInboundV1(chan) => chan.funding.is_outbound(), + ChannelPhase::UnfundedV2(chan) => chan.funding.is_outbound(), + ChannelPhase::PendingV2(chan) => chan.funding.is_outbound(), } } - #[cfg(any(test, feature = "_externalize_tests"))] - pub fn funding_mut(&mut self) -> &mut FundingScope { - match &mut self.phase { + pub fn get_channel_type(&self) -> &ChannelTypeFeatures { + match &self.phase { ChannelPhase::Undefined => unreachable!(), - ChannelPhase::Funded(chan) => &mut chan.funding, - ChannelPhase::UnfundedOutboundV1(chan) => &mut chan.funding, - ChannelPhase::UnfundedInboundV1(chan) => &mut chan.funding, - ChannelPhase::UnfundedV2(chan) => &mut chan.funding, + ChannelPhase::Funded(chan) => chan.funding.get_channel_type(), + ChannelPhase::UnfundedOutboundV1(chan) => chan.funding.get_channel_type(), + ChannelPhase::PendingV1(chan) => chan.funding.get_channel_type(), + ChannelPhase::UnfundedInboundV1(chan) => chan.funding.get_channel_type(), + ChannelPhase::UnfundedV2(chan) => chan.funding.get_channel_type(), + ChannelPhase::PendingV2(chan) => chan.funding.get_channel_type(), } } - pub fn funding_and_context_mut(&mut self) -> (&FundingScope, &mut ChannelContext) { - match &mut self.phase { + pub fn get_funding_txo(&self) -> Option { + match &self.phase { + ChannelPhase::Undefined => unreachable!(), + ChannelPhase::Funded(chan) => chan.funding.get_funding_txo(), + ChannelPhase::UnfundedOutboundV1(chan) => chan.funding.get_funding_txo(), + ChannelPhase::PendingV1(chan) => chan.funding.get_funding_txo(), + ChannelPhase::UnfundedInboundV1(chan) => chan.funding.get_funding_txo(), + ChannelPhase::UnfundedV2(chan) => chan.funding.get_funding_txo(), + ChannelPhase::PendingV2(chan) => chan.funding.get_funding_txo(), + } + } + + #[cfg(test)] + pub fn get_short_channel_id(&self) -> Option { + match &self.phase { + ChannelPhase::Undefined => unreachable!(), + ChannelPhase::Funded(chan) => chan.funding.get_short_channel_id(), + ChannelPhase::UnfundedOutboundV1(chan) => chan.funding.get_short_channel_id(), + ChannelPhase::PendingV1(chan) => chan.funding.get_short_channel_id(), + ChannelPhase::UnfundedInboundV1(chan) => chan.funding.get_short_channel_id(), + ChannelPhase::UnfundedV2(chan) => chan.funding.get_short_channel_id(), + ChannelPhase::PendingV2(chan) => chan.funding.get_short_channel_id(), + } + } + + #[rustfmt::skip] + pub(super) fn channel_details( + &self, best_block_height: u32, latest_features: InitFeatures, + fee_estimator: &LowerBoundedFeeEstimator, + ) -> super::channel_state::ChannelDetails { + let context = self.context(); + let balance = self.get_available_balances(fee_estimator).unwrap_or_else(|()| { + debug_assert!(false, "some channel balance has been overdrawn"); + AvailableBalances { + inbound_capacity_msat: 0, + outbound_capacity_msat: 0, + next_outbound_htlc_limit_msat: 0, + next_outbound_htlc_minimum_msat: u64::MAX, + } + }); + let minimum_depth = self.minimum_depth(); + match &self.phase { ChannelPhase::Undefined => unreachable!(), - ChannelPhase::Funded(chan) => (&chan.funding, &mut chan.context), - ChannelPhase::UnfundedOutboundV1(chan) => (&chan.funding, &mut chan.context), - ChannelPhase::UnfundedInboundV1(chan) => (&chan.funding, &mut chan.context), - ChannelPhase::UnfundedV2(chan) => (&chan.funding, &mut chan.context), + ChannelPhase::Funded(chan) => super::channel_state::ChannelDetails::from_channel_parts( + context, &chan.funding, balance, minimum_depth, best_block_height, latest_features, fee_estimator, + ), + ChannelPhase::UnfundedOutboundV1(chan) => super::channel_state::ChannelDetails::from_channel_parts( + context, &chan.funding, balance, minimum_depth, best_block_height, latest_features, fee_estimator, + ), + ChannelPhase::PendingV1(chan) => super::channel_state::ChannelDetails::from_channel_parts( + context, &chan.funding, balance, minimum_depth, best_block_height, latest_features, fee_estimator, + ), + ChannelPhase::UnfundedInboundV1(chan) => super::channel_state::ChannelDetails::from_channel_parts( + context, &chan.funding, balance, minimum_depth, best_block_height, latest_features, fee_estimator, + ), + ChannelPhase::UnfundedV2(chan) => super::channel_state::ChannelDetails::from_channel_parts( + context, &chan.funding, balance, minimum_depth, best_block_height, latest_features, fee_estimator, + ), + ChannelPhase::PendingV2(chan) => super::channel_state::ChannelDetails::from_channel_parts( + context, &chan.funding, balance, minimum_depth, best_block_height, latest_features, fee_estimator, + ), } } @@ -1556,8 +1620,10 @@ where None }, ChannelPhase::UnfundedOutboundV1(chan) => Some(&mut chan.unfunded_context), + ChannelPhase::PendingV1(chan) => Some(&mut chan.unfunded_context), ChannelPhase::UnfundedInboundV1(chan) => Some(&mut chan.unfunded_context), ChannelPhase::UnfundedV2(chan) => Some(&mut chan.unfunded_context), + ChannelPhase::PendingV2(chan) => Some(&mut chan.unfunded_context), } } @@ -1589,11 +1655,22 @@ where } } + #[cfg(any(test, feature = "_externalize_tests"))] + pub fn as_unfunded_inbound_v1_mut(&mut self) -> Option<&mut InboundV1Channel> { + if let ChannelPhase::UnfundedInboundV1(channel) = &mut self.phase { + Some(channel) + } else { + None + } + } + #[cfg(any(test, feature = "_externalize_tests"))] pub fn is_unfunded_v1(&self) -> bool { matches!( self.phase, - ChannelPhase::UnfundedOutboundV1(_) | ChannelPhase::UnfundedInboundV1(_) + ChannelPhase::UnfundedOutboundV1(_) + | ChannelPhase::PendingV1(_) + | ChannelPhase::UnfundedInboundV1(_) ) } @@ -1601,7 +1678,7 @@ where /// /// If this method returns true, [`Self::into_unfunded_outbound_v1`] will also succeed. pub fn ready_to_fund(&self) -> bool { - if !self.funding().is_outbound() { + if !self.is_outbound() { return false; } match self.context().channel_state { @@ -1629,7 +1706,7 @@ where } } - pub fn as_unfunded_v2(&self) -> Option<&PendingV2Channel> { + pub fn as_unfunded_v2(&self) -> Option<&UnfundedV2Channel> { if let ChannelPhase::UnfundedV2(channel) = &self.phase { Some(channel) } else { @@ -1645,12 +1722,30 @@ where ChannelPhase::Undefined => unreachable!(), ChannelPhase::Funded(chan) => chan.signer_maybe_unblocked(logger, path_for_release_htlc).map(|r| Some(r)), ChannelPhase::UnfundedOutboundV1(chan) => { - let (open_channel, funding_created) = chan.signer_maybe_unblocked(chain_hash, logger); + let open_channel = chan.signer_maybe_unblocked(chain_hash, logger); Ok(Some(SignerResumeUpdates { commitment_update: None, revoke_and_ack: None, open_channel, accept_channel: None, + funding_created: None, + funding_signed: None, + funding_commit_sig: None, + tx_signatures: None, + channel_ready: None, + order: chan.context.resend_order.clone(), + closing_signed: None, + signed_closing_tx: None, + shutdown_result: None, + })) + }, + ChannelPhase::PendingV1(chan) => { + let funding_created = chan.signer_maybe_unblocked(logger); + Ok(Some(SignerResumeUpdates { + commitment_update: None, + revoke_and_ack: None, + open_channel: None, + accept_channel: None, funding_created, funding_signed: None, funding_commit_sig: None, @@ -1680,7 +1775,7 @@ where shutdown_result: None, })) }, - ChannelPhase::UnfundedV2(_) => Ok(None), + ChannelPhase::UnfundedV2(_) | ChannelPhase::PendingV2(_) => Ok(None), } } @@ -1700,8 +1795,11 @@ where // handshake (and bailing if the peer rejects it), so we force-close in // that case. ChannelPhase::UnfundedOutboundV1(chan) => chan.is_resumable(), + // PendingV1 has committed to a funding transaction so we don't yet + // support replaying the funding handshake on reconnection. + ChannelPhase::PendingV1(_) => false, ChannelPhase::UnfundedInboundV1(_) => false, - ChannelPhase::UnfundedV2(_) => false, + ChannelPhase::UnfundedV2(_) | ChannelPhase::PendingV2(_) => false, }; let splice_funding_failed = if let ChannelPhase::Funded(chan) = &mut self.phase { @@ -1749,6 +1847,11 @@ where .map(|msg| ReconnectionMsg::Open(OpenChannelMessage::V1(msg))) .unwrap_or(ReconnectionMsg::None) }, + ChannelPhase::PendingV1(_) => { + // PendingV1 channels are not resumable, so this shouldn't be reached. + debug_assert!(false); + ReconnectionMsg::None + }, ChannelPhase::UnfundedInboundV1(_) => { // Since unfunded inbound channel maps are cleared upon disconnecting a peer, // they are not persisted and won't be recovered after a crash. @@ -1769,6 +1872,11 @@ where ReconnectionMsg::None } }, + ChannelPhase::PendingV2(_) => { + // PendingV2 channels are not resumable, so this shouldn't be reached. + debug_assert!(false); + ReconnectionMsg::None + }, } } @@ -1787,6 +1895,7 @@ where ) .map(|msg| Some(OpenChannelMessage::V1(msg))) }, + ChannelPhase::PendingV1(_) | ChannelPhase::PendingV2(_) => Ok(None), ChannelPhase::UnfundedInboundV1(_) => Ok(None), ChannelPhase::UnfundedV2(chan) => { if chan.funding.is_outbound() { @@ -1817,9 +1926,10 @@ where let (splice_funding_failed, exited_quiescence) = match &mut self.phase { ChannelPhase::Undefined => unreachable!(), - ChannelPhase::UnfundedOutboundV1(_) | ChannelPhase::UnfundedInboundV1(_) => { - (None, false) - }, + ChannelPhase::UnfundedOutboundV1(_) + | ChannelPhase::PendingV1(_) + | ChannelPhase::PendingV2(_) + | ChannelPhase::UnfundedInboundV1(_) => (None, false), ChannelPhase::UnfundedV2(pending_v2_channel) => { pending_v2_channel.interactive_tx_constructor.take(); (None, false) @@ -1990,7 +2100,9 @@ where // https://github.com/lightning/bolts/blob/247e83d/02-peer-protocol.md?plain=1#L578-L580 let (should_ack, splice_funding_failed, exited_quiescence) = match &mut self.phase { ChannelPhase::Undefined => unreachable!(), - ChannelPhase::UnfundedOutboundV1(_) | ChannelPhase::UnfundedInboundV1(_) => { + ChannelPhase::UnfundedOutboundV1(_) + | ChannelPhase::PendingV1(_) + | ChannelPhase::UnfundedInboundV1(_) => { let err = "Got an unexpected tx_abort message: This is an unfunded channel created with V1 channel establishment"; return Err(ChannelError::Warn(err.into())); }, @@ -1999,6 +2111,10 @@ where pending_v2_channel.interactive_tx_constructor.take().is_some(); (had_constructor, None, false) }, + ChannelPhase::PendingV2(chan) => { + let had_session = chan.context.interactive_tx_signing_session.take().is_some(); + (had_session, None, false) + }, ChannelPhase::Funded(funded_channel) => { if funded_channel.has_pending_splice_awaiting_signatures() && funded_channel @@ -2053,7 +2169,7 @@ where &mut self, msg: &msgs::FundingSigned, best_block: BestBlock, signer_provider: &SP, logger: &L ) -> Result<(&mut FundedChannel, ChannelMonitor), ChannelError> { let phase = core::mem::replace(&mut self.phase, ChannelPhase::Undefined); - let result = if let ChannelPhase::UnfundedOutboundV1(chan) = phase { + let result = if let ChannelPhase::PendingV1(chan) = phase { let channel_state = chan.context.channel_state; let logger = WithChannelContext::from(logger, &chan.context, None); match chan.funding_signed(msg, best_block, signer_provider, &&logger) { @@ -2064,22 +2180,57 @@ where }, Err((chan, e)) => { debug_assert_eq!(chan.context.channel_state, channel_state); - self.phase = ChannelPhase::UnfundedOutboundV1(chan); + self.phase = ChannelPhase::PendingV1(chan); Err(e) }, } } else { self.phase = phase; - Err(ChannelError::SendError("Failed to find corresponding UnfundedOutboundV1 channel".to_owned())) + Err(ChannelError::SendError("Failed to find corresponding PendingV1 channel".to_owned())) }; debug_assert!(!matches!(self.phase, ChannelPhase::Undefined)); result.map(|monitor| (self.as_funded_mut().expect("Channel should be funded"), monitor)) } + /// Reverts a channel from `Funded` back to `UnfundedOutboundV1`, clearing the funding + /// outpoint. Used when `watch_channel` fails after `funding_signed` to ensure + /// `force_shutdown` doesn't generate a monitor update for an unregistered channel. + /// + /// The channel must be immediately shut down after this call. + pub(super) fn unfund(&mut self) { + let phase = core::mem::replace(&mut self.phase, ChannelPhase::Undefined); + self.phase = if let ChannelPhase::Funded(chan) = phase { + debug_assert!(matches!( + chan.context.channel_state, + ChannelState::AwaitingChannelReady(_) + )); + let mut funding = chan.funding.into_partial(); + funding.channel_transaction_parameters.funding_outpoint = None; + let channel_id = chan.context.temporary_channel_id.expect( + "temporary_channel_id should be set since unfund is only called on \ + channels that were unfunded immediately beforehand", + ); + let mut context = chan.context; + context.channel_id = channel_id; + ChannelPhase::UnfundedOutboundV1(OutboundV1Channel { + funding, + context, + unfunded_context: UnfundedChannelContext { + unfunded_channel_age_ticks: 0, + holder_commitment_point: Some(chan.holder_commitment_point), + }, + signer_pending_open_channel: false, + }) + } else { + panic!("unfund called on non-Funded channel"); + }; + } + fn funding_tx_constructed(&mut self, funding_outpoint: OutPoint) -> Result<(), AbortReason> { - let interactive_tx_constructor = match &mut self.phase { - ChannelPhase::UnfundedV2(chan) => { + let phase = core::mem::replace(&mut self.phase, ChannelPhase::Undefined); + let result = match phase { + ChannelPhase::UnfundedV2(mut chan) => { debug_assert_eq!( chan.context.channel_state, ChannelState::NegotiatingFunding( @@ -2097,12 +2248,27 @@ where chan.funding.channel_transaction_parameters.funding_outpoint = Some(funding_outpoint); - chan.interactive_tx_constructor + let interactive_tx_constructor = chan + .interactive_tx_constructor .take() - .expect("PendingV2Channel::interactive_tx_constructor should be set") + .expect("UnfundedV2Channel::interactive_tx_constructor should be set"); + let funding = chan + .funding + .into_complete() + .expect("funding params must be complete after tx construction"); + + let signing_session = interactive_tx_constructor.into_signing_session(); + chan.context.interactive_tx_signing_session = Some(signing_session); + + self.phase = ChannelPhase::PendingV2(PendingV2Channel { + funding, + context: chan.context, + unfunded_context: chan.unfunded_context, + }); + Ok(()) }, - ChannelPhase::Funded(chan) => { - if let Some(pending_splice) = chan.pending_splice.as_mut() { + ChannelPhase::Funded(mut chan) => { + let result = if let Some(pending_splice) = chan.pending_splice.as_mut() { let funding_negotiation = pending_splice.funding_negotiation.take(); if let Some(FundingNegotiation::ConstructingTransaction { mut funding, @@ -2112,37 +2278,41 @@ where let is_initiator = interactive_tx_constructor.is_initiator(); funding.channel_transaction_parameters.funding_outpoint = Some(funding_outpoint); + let funding = funding + .into_complete() + .expect("funding params must be complete after tx construction"); pending_splice.funding_negotiation = Some(FundingNegotiation::AwaitingSignatures { is_initiator, funding, initial_commitment_signed_from_counterparty: None, }); - interactive_tx_constructor + + let signing_session = interactive_tx_constructor.into_signing_session(); + chan.context.interactive_tx_signing_session = Some(signing_session); + Ok(()) } else { // Replace the taken state for later error handling pending_splice.funding_negotiation = funding_negotiation; - return Err(AbortReason::InternalError( + Err(AbortReason::InternalError( "Got a tx_complete message in an invalid state", - )); + )) } } else { - return Err(AbortReason::InternalError( - "Got a tx_complete message in an invalid state", - )); - } + Err(AbortReason::InternalError("Got a tx_complete message in an invalid state")) + }; + self.phase = ChannelPhase::Funded(chan); + result }, _ => { + self.phase = phase; debug_assert!(false); - return Err(AbortReason::InternalError( - "Got a tx_complete message in an invalid phase", - )); + Err(AbortReason::InternalError("Got a tx_complete message in an invalid phase")) }, }; - let signing_session = interactive_tx_constructor.into_signing_session(); - self.context_mut().interactive_tx_signing_session = Some(signing_session); - Ok(()) + debug_assert!(!matches!(self.phase, ChannelPhase::Undefined)); + result } pub fn funding_transaction_signed( @@ -2151,7 +2321,7 @@ where ) -> Result { let (context, funding, pending_splice) = match &mut self.phase { ChannelPhase::Undefined => unreachable!(), - ChannelPhase::UnfundedV2(channel) => (&mut channel.context, &channel.funding, None), + ChannelPhase::PendingV2(channel) => (&mut channel.context, &channel.funding, None), ChannelPhase::Funded(channel) => { (&mut channel.context, &channel.funding, channel.pending_splice.as_ref()) }, @@ -2197,7 +2367,7 @@ where signing_session } else { - if Some(funding_txid_signed) == funding.get_funding_txid() { + if funding_txid_signed == funding.get_funding_txid() { // We may be handling a duplicate call and the funding was already locked so we // no longer have the signing session present. return Ok(FundingTxSigned { @@ -2256,12 +2426,15 @@ where ); } - let funding = pending_splice + let commitment_signed = if let Some(splice_funding) = pending_splice .as_ref() .and_then(|pending_splice| pending_splice.funding_negotiation.as_ref()) .and_then(|funding_negotiation| funding_negotiation.as_funding()) - .unwrap_or(funding); - let commitment_signed = context.get_initial_commitment_signed_v2(funding, &&logger); + { + context.get_initial_commitment_signed_v2(splice_funding, &&logger) + } else { + context.get_initial_commitment_signed_v2(funding, &&logger) + }; // For zero conf channels, we don't expect the funding transaction to be ready for broadcast // yet as, according to the spec, our counterparty shouldn't have sent their `tx_signatures` @@ -2326,8 +2499,27 @@ where } pub fn force_shutdown(&mut self, closure_reason: ClosureReason) -> ShutdownResult { - let (funding, context) = self.funding_and_context_mut(); - context.force_shutdown(funding, closure_reason) + match &mut self.phase { + ChannelPhase::Undefined => unreachable!(), + ChannelPhase::Funded(chan) => { + chan.context.force_shutdown(&chan.funding, closure_reason) + }, + ChannelPhase::UnfundedOutboundV1(chan) => { + chan.context.force_shutdown(&chan.funding, closure_reason) + }, + ChannelPhase::PendingV1(chan) => { + chan.context.force_shutdown(&chan.funding, closure_reason) + }, + ChannelPhase::UnfundedInboundV1(chan) => { + chan.context.force_shutdown(&chan.funding, closure_reason) + }, + ChannelPhase::UnfundedV2(chan) => { + chan.context.force_shutdown(&chan.funding, closure_reason) + }, + ChannelPhase::PendingV2(chan) => { + chan.context.force_shutdown(&chan.funding, closure_reason) + }, + } } #[rustfmt::skip] @@ -2336,7 +2528,7 @@ where ) -> Result<(Option>, Option), ChannelError> { let phase = core::mem::replace(&mut self.phase, ChannelPhase::Undefined); match phase { - ChannelPhase::UnfundedV2(chan) => { + ChannelPhase::PendingV2(chan) => { let holder_commitment_point = match chan.unfunded_context.holder_commitment_point { Some(point) => point, None => { @@ -2438,17 +2630,31 @@ where ChannelPhase::UnfundedOutboundV1(chan) => { chan.context.get_available_balances_for_scope(&chan.funding, fee_estimator) }, + ChannelPhase::PendingV1(chan) => { + chan.context.get_available_balances_for_scope(&chan.funding, fee_estimator) + }, ChannelPhase::UnfundedInboundV1(chan) => { chan.context.get_available_balances_for_scope(&chan.funding, fee_estimator) }, ChannelPhase::UnfundedV2(chan) => { chan.context.get_available_balances_for_scope(&chan.funding, fee_estimator) }, + ChannelPhase::PendingV2(chan) => { + chan.context.get_available_balances_for_scope(&chan.funding, fee_estimator) + }, } } pub fn minimum_depth(&self) -> Option { - self.context().minimum_depth(self.funding()) + match &self.phase { + ChannelPhase::Undefined => unreachable!(), + ChannelPhase::Funded(chan) => chan.context.minimum_depth(&chan.funding), + ChannelPhase::UnfundedOutboundV1(chan) => chan.context.minimum_depth(&chan.funding), + ChannelPhase::PendingV1(chan) => chan.context.minimum_depth(&chan.funding), + ChannelPhase::UnfundedInboundV1(chan) => chan.context.minimum_depth(&chan.funding), + ChannelPhase::UnfundedV2(chan) => chan.context.minimum_depth(&chan.funding), + ChannelPhase::PendingV2(chan) => chan.context.minimum_depth(&chan.funding), + } } } @@ -2461,6 +2667,15 @@ where } } +impl From> for Channel +where + SP::EcdsaSigner: ChannelSigner, +{ + fn from(channel: PendingV1Channel) -> Self { + Channel { phase: ChannelPhase::PendingV1(channel) } + } +} + impl From> for Channel where SP::EcdsaSigner: ChannelSigner, @@ -2470,12 +2685,21 @@ where } } +impl From> for Channel +where + SP::EcdsaSigner: ChannelSigner, +{ + fn from(channel: UnfundedV2Channel) -> Self { + Channel { phase: ChannelPhase::UnfundedV2(channel) } + } +} + impl From> for Channel where SP::EcdsaSigner: ChannelSigner, { fn from(channel: PendingV2Channel) -> Self { - Channel { phase: ChannelPhase::UnfundedV2(channel) } + Channel { phase: ChannelPhase::PendingV2(channel) } } } @@ -2523,7 +2747,7 @@ impl UnfundedChannelContext { /// during channel establishment and may be replaced during channel splicing or if the attempted /// funding transaction is replaced using tx_init_rbf. #[derive(Debug)] -pub(super) struct FundingScope { +pub(super) struct FundingScope { value_to_self_msat: u64, // Excluding all pending_htlcs, fees, and anchor outputs /// minimum channel reserve for self to maintain - set by them. @@ -2549,7 +2773,7 @@ pub(super) struct FundingScope { #[cfg(any(test, fuzzing))] next_remote_fee: Mutex, - pub(super) channel_transaction_parameters: ChannelTransactionParameters, + pub(super) channel_transaction_parameters: P, /// The transaction which funds this channel. Note that for manually-funded channels (i.e., /// [`ChannelContext::is_manual_broadcast`] is true) this will be a dummy empty transaction. @@ -2564,7 +2788,7 @@ pub(super) struct FundingScope { minimum_depth_override: Option, } -impl Writeable for FundingScope { +impl Writeable for FundingScope { fn write(&self, writer: &mut W) -> Result<(), io::Error> { write_tlv_fields!(writer, { (1, self.value_to_self_msat, required), @@ -2581,13 +2805,20 @@ impl Writeable for FundingScope { } } -impl Readable for FundingScope { +impl Readable for FundingScope { + fn read(reader: &mut R) -> Result { + let funding = FundingScope::::read(reader)?; + funding.into_complete().ok_or(DecodeError::InvalidValue) + } +} + +impl Readable for FundingScope { #[rustfmt::skip] fn read(reader: &mut R) -> Result { let mut value_to_self_msat = RequiredWrapper(None); let mut counterparty_selected_channel_reserve_satoshis = None; let mut holder_selected_channel_reserve_satoshis = RequiredWrapper(None); - let mut channel_transaction_parameters = RequiredWrapper(None); + let mut channel_transaction_parameters: RequiredWrapper = RequiredWrapper(None); let mut funding_transaction = None; let mut funding_tx_confirmed_in = None; let mut funding_tx_confirmation_height = RequiredWrapper(None); @@ -2598,7 +2829,7 @@ impl Readable for FundingScope { (1, value_to_self_msat, required), (3, counterparty_selected_channel_reserve_satoshis, option), (5, holder_selected_channel_reserve_satoshis, required), - (7, channel_transaction_parameters, (required: ReadableArgs, None)), + (7, channel_transaction_parameters, (required: ReadableArgs, None::)), (9, funding_transaction, option), (11, funding_tx_confirmed_in, option), (13, funding_tx_confirmation_height, required), @@ -2620,6 +2851,7 @@ impl Readable for FundingScope { funding_tx_confirmation_height: funding_tx_confirmation_height.0.unwrap(), short_channel_id, minimum_depth_override, + #[cfg(any(test, fuzzing))] next_local_fee: Mutex::new(PredictedNextFee::default()), #[cfg(any(test, fuzzing))] @@ -2628,9 +2860,9 @@ impl Readable for FundingScope { } } -impl FundingScope { +impl FundingScope

{ pub fn get_value_satoshis(&self) -> u64 { - self.channel_transaction_parameters.channel_value_satoshis + self.channel_transaction_parameters.channel_value_satoshis() } pub(crate) fn get_value_to_self_msat(&self) -> u64 { @@ -2655,13 +2887,13 @@ impl FundingScope { } pub fn is_outbound(&self) -> bool { - self.channel_transaction_parameters.is_outbound_from_holder + self.channel_transaction_parameters.is_outbound_from_holder() } /// Returns the funding_txo we either got from our peer, or were given by /// get_funding_created. pub fn get_funding_txo(&self) -> Option { - self.channel_transaction_parameters.funding_outpoint + self.channel_transaction_parameters.funding_outpoint() } /// Gets the funding output for this channel, if available. @@ -2678,25 +2910,26 @@ impl FundingScope { }) } - fn get_funding_txid(&self) -> Option { - self.channel_transaction_parameters.funding_outpoint.map(|txo| txo.txid) - } - fn get_holder_selected_contest_delay(&self) -> u16 { - self.channel_transaction_parameters.holder_selected_contest_delay + self.channel_transaction_parameters.holder_selected_contest_delay() } fn get_holder_pubkeys(&self) -> &ChannelPublicKeys { - &self.channel_transaction_parameters.holder_pubkeys + self.channel_transaction_parameters.holder_pubkeys() } pub fn get_counterparty_selected_contest_delay(&self) -> Option { - let params_opt = self.channel_transaction_parameters.counterparty_parameters.as_ref(); - params_opt.map(|params| params.selected_contest_delay) + self.channel_transaction_parameters + .counterparty_parameters() + .map(|params| params.selected_contest_delay) } fn get_counterparty_pubkeys(&self) -> &ChannelPublicKeys { - &self.channel_transaction_parameters.counterparty_parameters.as_ref().unwrap().pubkeys + &self + .channel_transaction_parameters + .counterparty_parameters() + .expect("counterparty_parameters must be set") + .pubkeys } /// Gets the redeemscript for the funding transaction output (ie the funding transaction output @@ -2716,7 +2949,7 @@ impl FundingScope { /// Gets the channel's type pub fn get_channel_type(&self) -> &ChannelTypeFeatures { - &self.channel_transaction_parameters.channel_type_features + self.channel_transaction_parameters.channel_type_features() } /// Returns the height in which our funding transaction was confirmed. @@ -2745,12 +2978,40 @@ impl FundingScope { pub fn get_short_channel_id(&self) -> Option { self.short_channel_id } +} + +impl FundingScope { + /// Converts this partial `FundingScope` into a `FundingScope` with complete parameters. + /// + /// Returns `None` if the channel transaction parameters are not fully populated. + fn into_complete(self) -> Option> { + Some(FundingScope { + channel_transaction_parameters: self.channel_transaction_parameters.into_complete()?, + value_to_self_msat: self.value_to_self_msat, + counterparty_selected_channel_reserve_satoshis: self + .counterparty_selected_channel_reserve_satoshis, + holder_selected_channel_reserve_satoshis: self.holder_selected_channel_reserve_satoshis, + #[cfg(debug_assertions)] + holder_prev_commitment_tx_balance: self.holder_prev_commitment_tx_balance, + #[cfg(debug_assertions)] + counterparty_prev_commitment_tx_balance: self.counterparty_prev_commitment_tx_balance, + #[cfg(any(test, fuzzing))] + next_local_fee: self.next_local_fee, + #[cfg(any(test, fuzzing))] + next_remote_fee: self.next_remote_fee, + funding_transaction: self.funding_transaction, + funding_tx_confirmed_in: self.funding_tx_confirmed_in, + funding_tx_confirmation_height: self.funding_tx_confirmation_height, + short_channel_id: self.short_channel_id, + minimum_depth_override: self.minimum_depth_override, + }) + } /// Constructs a `FundingScope` for splicing a channel. fn for_splice( - prev_funding: &Self, context: &ChannelContext, our_funding_contribution: SignedAmount, - their_funding_contribution: SignedAmount, counterparty_funding_pubkey: PublicKey, - our_new_holder_keys: ChannelPublicKeys, + prev_funding: &FundingScope, context: &ChannelContext, + our_funding_contribution: SignedAmount, their_funding_contribution: SignedAmount, + counterparty_funding_pubkey: PublicKey, our_new_holder_keys: ChannelPublicKeys, ) -> Self { debug_assert!(our_funding_contribution.unsigned_abs() <= Amount::MAX_MONEY); debug_assert!(their_funding_contribution.unsigned_abs() <= Amount::MAX_MONEY); @@ -2767,15 +3028,15 @@ impl FundingScope { let post_value_to_self_msat = post_value_to_self_msat.unwrap(); let channel_parameters = &prev_funding.channel_transaction_parameters; - let mut post_channel_transaction_parameters = ChannelTransactionParameters { + let mut post_channel_transaction_parameters = PartialChannelTransactionParameters { holder_pubkeys: our_new_holder_keys, - holder_selected_contest_delay: channel_parameters.holder_selected_contest_delay, + holder_selected_contest_delay: channel_parameters.holder_selected_contest_delay(), // The 'outbound' attribute doesn't change, even if the splice initiator is the other node - is_outbound_from_holder: channel_parameters.is_outbound_from_holder, - counterparty_parameters: channel_parameters.counterparty_parameters.clone(), + is_outbound_from_holder: channel_parameters.is_outbound_from_holder(), + counterparty_parameters: channel_parameters.counterparty_parameters().cloned(), funding_outpoint: None, // filled later - splice_parent_funding_txid: prev_funding.get_funding_txid(), - channel_type_features: channel_parameters.channel_type_features.clone(), + splice_parent_funding_txid: Some(prev_funding.get_funding_txid()), + channel_type_features: channel_parameters.channel_type_features().clone(), channel_value_satoshis: post_channel_value, }; post_channel_transaction_parameters @@ -2841,6 +3102,37 @@ impl FundingScope { short_channel_id: None, } } +} + +impl FundingScope { + fn get_funding_txid(&self) -> Txid { + self.channel_transaction_parameters.funding_outpoint.txid + } + + /// Converts this `FundingScope` with complete parameters back into one with partial + /// parameters. Used when reverting a channel from funded to unfunded state. + fn into_partial(self) -> FundingScope { + FundingScope { + channel_transaction_parameters: self.channel_transaction_parameters.into_partial(), + value_to_self_msat: self.value_to_self_msat, + counterparty_selected_channel_reserve_satoshis: self + .counterparty_selected_channel_reserve_satoshis, + holder_selected_channel_reserve_satoshis: self.holder_selected_channel_reserve_satoshis, + #[cfg(debug_assertions)] + holder_prev_commitment_tx_balance: self.holder_prev_commitment_tx_balance, + #[cfg(debug_assertions)] + counterparty_prev_commitment_tx_balance: self.counterparty_prev_commitment_tx_balance, + #[cfg(any(test, fuzzing))] + next_local_fee: self.next_local_fee, + #[cfg(any(test, fuzzing))] + next_remote_fee: self.next_remote_fee, + funding_transaction: self.funding_transaction, + funding_tx_confirmed_in: self.funding_tx_confirmed_in, + funding_tx_confirmation_height: self.funding_tx_confirmation_height, + short_channel_id: self.short_channel_id, + minimum_depth_override: self.minimum_depth_override, + } + } /// Compute the post-splice channel value from each counterparty's contributions. pub(super) fn compute_post_splice_value( @@ -2889,7 +3181,7 @@ struct PendingFunding { /// Funding candidates that have been negotiated but have not reached enough confirmations /// by both counterparties to have exchanged `splice_locked` and be promoted. - negotiated_candidates: Vec, + negotiated_candidates: Vec>, /// The funding txid used in the `splice_locked` sent to the counterparty. sent_funding_txid: Option, @@ -2912,11 +3204,11 @@ enum FundingNegotiation { new_holder_funding_key: PublicKey, }, ConstructingTransaction { - funding: FundingScope, + funding: FundingScope, interactive_tx_constructor: InteractiveTxConstructor, }, AwaitingSignatures { - funding: FundingScope, + funding: FundingScope, is_initiator: bool, /// The initial [`msgs::CommitmentSigned`] message received for the [`FundingScope`] above. /// We delay processing this until the user manually approves the splice via @@ -2942,11 +3234,35 @@ impl_writeable_tlv_based_enum_upgradable!(FundingNegotiation, ); impl FundingNegotiation { - fn as_funding(&self) -> Option<&FundingScope> { + /// Returns the funding from `AwaitingSignatures`, which has complete channel parameters. + fn as_funding(&self) -> Option<&FundingScope> { match self { - FundingNegotiation::AwaitingAck { .. } => None, - FundingNegotiation::ConstructingTransaction { funding, .. } => Some(funding), FundingNegotiation::AwaitingSignatures { funding, .. } => Some(funding), + _ => None, + } + } + + /// Returns the funding txo from whichever negotiation state has funding. + fn get_funding_txo(&self) -> Option { + match self { + FundingNegotiation::AwaitingAck { .. } => None, + FundingNegotiation::ConstructingTransaction { funding, .. } => { + funding.get_funding_txo() + }, + FundingNegotiation::AwaitingSignatures { funding, .. } => funding.get_funding_txo(), + } + } + + /// Returns the channel type from whichever negotiation state has funding. + fn get_channel_type(&self) -> Option<&ChannelTypeFeatures> { + match self { + FundingNegotiation::AwaitingAck { .. } => None, + FundingNegotiation::ConstructingTransaction { funding, .. } => { + Some(funding.get_channel_type()) + }, + FundingNegotiation::AwaitingSignatures { funding, .. } => { + Some(funding.get_channel_type()) + }, } } @@ -2972,13 +3288,7 @@ impl PendingFunding { return None; } - let confirmed_funding_txid = match funding.get_funding_txid() { - Some(funding_txid) => funding_txid, - None => { - debug_assert!(false); - return None; - }, - }; + let confirmed_funding_txid = funding.get_funding_txid(); match self.sent_funding_txid { Some(sent_funding_txid) if confirmed_funding_txid == sent_funding_txid => None, @@ -3348,221 +3658,6 @@ pub(super) struct ChannelContext { pub interactive_tx_signing_session: Option, } -/// A channel struct implementing this trait can receive an initial counterparty commitment -/// transaction signature. -trait InitialRemoteCommitmentReceiver { - fn context(&self) -> &ChannelContext; - - fn context_mut(&mut self) -> &mut ChannelContext; - - fn funding(&self) -> &FundingScope; - - fn funding_mut(&mut self) -> &mut FundingScope; - - fn received_msg(&self) -> &'static str; - - #[rustfmt::skip] - fn check_counterparty_commitment_signature( - &self, sig: &Signature, holder_commitment_point: &HolderCommitmentPoint, logger: &L - ) -> Result { - let funding_script = self.funding().get_funding_redeemscript(); - - let commitment_data = self.context().build_commitment_transaction(self.funding(), - holder_commitment_point.next_transaction_number(), &holder_commitment_point.next_point(), - true, false, logger); - let initial_commitment_tx = commitment_data.tx; - let trusted_tx = initial_commitment_tx.trust(); - let initial_commitment_bitcoin_tx = trusted_tx.built_transaction(); - let sighash = initial_commitment_bitcoin_tx.get_sighash_all(&funding_script, self.funding().get_value_satoshis()); - // They sign the holder commitment transaction... - log_trace!(logger, "Checking {} tx signature {} by key {} against tx {} (sighash {}) with redeemscript {} for channel {}.", - self.received_msg(), log_bytes!(sig.serialize_compact()[..]), log_bytes!(self.funding().counterparty_funding_pubkey().serialize()), - encode::serialize_hex(&initial_commitment_bitcoin_tx.transaction), log_bytes!(sighash[..]), - encode::serialize_hex(&funding_script), &self.context().channel_id()); - secp_check!(self.context().secp_ctx.verify_ecdsa(&sighash, sig, self.funding().counterparty_funding_pubkey()), format!("Invalid {} signature from peer", self.received_msg())); - - Ok(initial_commitment_tx) - } - - #[rustfmt::skip] - fn initial_commitment_signed( - &mut self, channel_id: ChannelId, counterparty_signature: Signature, holder_commitment_point: &mut HolderCommitmentPoint, - best_block: BestBlock, signer_provider: &SP, logger: &L, - ) -> Result<(ChannelMonitor, CommitmentTransaction), ChannelError> { - let initial_commitment_tx = match self.check_counterparty_commitment_signature(&counterparty_signature, holder_commitment_point, logger) { - Ok(res) => res, - Err(ChannelError::Close(e)) => { - // TODO(dual_funding): Update for V2 established channels. - if !self.funding().is_outbound() { - self.funding_mut().channel_transaction_parameters.funding_outpoint = None; - } - return Err(ChannelError::Close(e)); - }, - Err(e) => { - // The only error we know how to handle is ChannelError::Close, so we fall over here - // to make sure we don't continue with an inconsistent state. - panic!("unexpected error type from check_counterparty_commitment_signature {:?}", e); - } - }; - let context = self.context(); - let commitment_data = context.build_commitment_transaction(self.funding(), - context.counterparty_next_commitment_transaction_number, - &context.counterparty_next_commitment_point.unwrap(), false, false, logger); - let counterparty_initial_commitment_tx = commitment_data.tx; - let counterparty_trusted_tx = counterparty_initial_commitment_tx.trust(); - let counterparty_initial_bitcoin_tx = counterparty_trusted_tx.built_transaction(); - - log_trace!(logger, "Initial counterparty tx for channel {} is: txid {} tx {}", - &context.channel_id(), counterparty_initial_bitcoin_tx.txid, encode::serialize_hex(&counterparty_initial_bitcoin_tx.transaction)); - - let holder_commitment_tx = HolderCommitmentTransaction::new( - initial_commitment_tx, - counterparty_signature, - Vec::new(), - &self.funding().get_holder_pubkeys().funding_pubkey, - &self.funding().counterparty_funding_pubkey() - ); - - if context.holder_signer.as_ref().validate_holder_commitment(&holder_commitment_tx, Vec::new()).is_err() { - return Err(ChannelError::close("Failed to validate our commitment".to_owned())); - } - - // Now that we're past error-generating stuff, update our local state: - - let is_v2_established = self.is_v2_established(); - let context = self.context_mut(); - context.channel_id = channel_id; - - assert!(!context.channel_state.is_monitor_update_in_progress()); // We have not had any monitor(s) yet to fail update! - if !is_v2_established { - if context.is_batch_funding() { - context.channel_state = ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::WAITING_FOR_BATCH); - } else { - context.channel_state = ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::new()); - } - } - if holder_commitment_point.advance(&context.holder_signer, &context.secp_ctx, logger).is_err() { - // We only fail to advance our commitment point/number if we're currently - // waiting for our signer to unblock and provide a commitment point. - // We cannot send accept_channel/open_channel before this has occurred, so if we - // err here by the time we receive funding_created/funding_signed, something has gone wrong. - debug_assert!(false, "We should be ready to advance our commitment point by the time we receive {}", self.received_msg()); - return Err(ChannelError::close("Failed to advance holder commitment point".to_owned())); - } - - let context = self.context(); - let funding = self.funding(); - let obscure_factor = get_commitment_transaction_number_obscure_factor(&funding.get_holder_pubkeys().payment_point, &funding.get_counterparty_pubkeys().payment_point, funding.is_outbound()); - let shutdown_script = context.shutdown_scriptpubkey.clone().map(|script| script.into_inner()); - let monitor_signer = signer_provider.derive_channel_signer(context.channel_keys_id); - // TODO(RBF): When implementing RBF, the funding_txo passed here must only update - // ChannelMonitorImp::first_confirmed_funding_txo during channel establishment, not splicing - let channel_monitor = ChannelMonitor::new( - context.secp_ctx.clone(), monitor_signer, shutdown_script, - funding.get_holder_selected_contest_delay(), &context.destination_script, - &funding.channel_transaction_parameters, funding.is_outbound(), obscure_factor, - holder_commitment_tx, best_block, context.counterparty_node_id, context.channel_id(), - context.is_manual_broadcast, - ); - channel_monitor.provide_initial_counterparty_commitment_tx( - counterparty_initial_commitment_tx.clone(), - ); - - self.context_mut().counterparty_next_commitment_transaction_number -= 1; - - Ok((channel_monitor, counterparty_initial_commitment_tx)) - } - - fn is_v2_established(&self) -> bool; -} - -impl InitialRemoteCommitmentReceiver for OutboundV1Channel { - fn context(&self) -> &ChannelContext { - &self.context - } - - fn context_mut(&mut self) -> &mut ChannelContext { - &mut self.context - } - - fn funding(&self) -> &FundingScope { - &self.funding - } - - fn funding_mut(&mut self) -> &mut FundingScope { - &mut self.funding - } - - fn received_msg(&self) -> &'static str { - "funding_signed" - } - - fn is_v2_established(&self) -> bool { - false - } -} - -impl InitialRemoteCommitmentReceiver for InboundV1Channel { - fn context(&self) -> &ChannelContext { - &self.context - } - - fn context_mut(&mut self) -> &mut ChannelContext { - &mut self.context - } - - fn funding(&self) -> &FundingScope { - &self.funding - } - - fn funding_mut(&mut self) -> &mut FundingScope { - &mut self.funding - } - - fn received_msg(&self) -> &'static str { - "funding_created" - } - - fn is_v2_established(&self) -> bool { - false - } -} - -impl InitialRemoteCommitmentReceiver for FundedChannel { - fn context(&self) -> &ChannelContext { - &self.context - } - - fn context_mut(&mut self) -> &mut ChannelContext { - &mut self.context - } - - fn funding(&self) -> &FundingScope { - &self.funding - } - - fn funding_mut(&mut self) -> &mut FundingScope { - &mut self.funding - } - - fn received_msg(&self) -> &'static str { - "commitment_signed" - } - - fn is_v2_established(&self) -> bool { - let channel_parameters = &self.funding().channel_transaction_parameters; - // This will return false if `counterparty_parameters` is `None`, but for a `FundedChannel`, it - // should never be `None`. - debug_assert!(channel_parameters.counterparty_parameters.is_some()); - channel_parameters.counterparty_parameters.as_ref().is_some_and(|counterparty_parameters| { - self.context().channel_id().is_v2_channel_id( - &channel_parameters.holder_pubkeys.revocation_basepoint, - &counterparty_parameters.pubkeys.revocation_basepoint, - ) - }) - } -} - impl ChannelContext { #[rustfmt::skip] fn new_for_inbound_channel<'a, ES: EntropySource, F: FeeEstimator, L: Logger>( @@ -3583,7 +3678,7 @@ impl ChannelContext { msg_channel_reserve_satoshis: u64, msg_push_msat: u64, open_channel_fields: msgs::CommonOpenChannelFields, - ) -> Result<(FundingScope, ChannelContext), ChannelError> { + ) -> Result<(FundingScope, ChannelContext), ChannelError> { let logger = WithContext::from(logger, Some(counterparty_node_id), Some(open_channel_fields.temporary_channel_id), None); let announce_for_forwarding = if (open_channel_fields.channel_flags & 1) == 1 { true } else { false }; @@ -3742,7 +3837,7 @@ impl ChannelContext { #[cfg(any(test, fuzzing))] next_remote_fee: Mutex::new(PredictedNextFee::default()), - channel_transaction_parameters: ChannelTransactionParameters { + channel_transaction_parameters: PartialChannelTransactionParameters { holder_pubkeys: pubkeys, holder_selected_contest_delay: config.channel_handshake_config.our_to_self_delay, is_outbound_from_holder: false, @@ -3760,6 +3855,7 @@ impl ChannelContext { funding_tx_confirmation_height: 0, short_channel_id: None, minimum_depth_override: None, + }; let channel_context = ChannelContext { user_id, @@ -3919,7 +4015,7 @@ impl ChannelContext { channel_keys_id: [u8; 32], holder_signer: SP::EcdsaSigner, _logger: L, - ) -> Result<(FundingScope, ChannelContext), APIError> { + ) -> Result<(FundingScope, ChannelContext), APIError> { // This will be updated with the counterparty contribution if this is a dual-funded channel let channel_value_satoshis = funding_satoshis; @@ -3991,7 +4087,7 @@ impl ChannelContext { #[cfg(any(test, fuzzing))] next_remote_fee: Mutex::new(PredictedNextFee::default()), - channel_transaction_parameters: ChannelTransactionParameters { + channel_transaction_parameters: PartialChannelTransactionParameters { holder_pubkeys: pubkeys, holder_selected_contest_delay: config.channel_handshake_config.our_to_self_delay, is_outbound_from_holder: true, @@ -4007,6 +4103,7 @@ impl ChannelContext { funding_tx_confirmation_height: 0, short_channel_id: None, minimum_depth_override: None, + }; let channel_context = Self { user_id, @@ -4322,7 +4419,9 @@ impl ChannelContext { self.temporary_channel_id } - pub(super) fn minimum_depth(&self, funding: &FundingScope) -> Option { + pub(super) fn minimum_depth( + &self, funding: &FundingScope

, + ) -> Option { funding.minimum_depth_override.or(self.minimum_depth) } @@ -4360,7 +4459,7 @@ impl ChannelContext { /// `accept_channel2` message. #[rustfmt::skip] pub fn do_accept_channel_checks( - &mut self, funding: &mut FundingScope, default_limits: &ChannelHandshakeLimits, + &mut self, funding: &mut FundingScope, default_limits: &ChannelHandshakeLimits, their_features: &InitFeatures, common_fields: &msgs::CommonAcceptChannelFields, channel_reserve_satoshis: u64, ) -> Result<(), ChannelError> { @@ -4500,7 +4599,9 @@ impl ChannelContext { } /// Allowed in any state (including after shutdown), but will return none before TheirInitSent - pub fn get_holder_htlc_maximum_msat(&self, funding: &FundingScope) -> Option { + pub fn get_holder_htlc_maximum_msat( + &self, funding: &FundingScope

, + ) -> Option { funding.get_htlc_maximum_msat(self.holder_max_htlc_value_in_flight_msat) } @@ -4510,7 +4611,9 @@ impl ChannelContext { } /// Allowed in any state (including after shutdown), but will return none before TheirInitSent - pub fn get_counterparty_htlc_maximum_msat(&self, funding: &FundingScope) -> Option { + pub fn get_counterparty_htlc_maximum_msat( + &self, funding: &FundingScope

, + ) -> Option { funding.get_htlc_maximum_msat(self.counterparty_max_htlc_value_in_flight_msat) } @@ -4676,15 +4779,6 @@ impl ChannelContext { } } - #[rustfmt::skip] - fn unset_funding_info(&mut self, funding: &mut FundingScope) { - funding.channel_transaction_parameters.funding_outpoint = None; - self.channel_id = self.temporary_channel_id.expect( - "temporary_channel_id should be set since unset_funding_info is only called on funded \ - channels that were unfunded immediately beforehand" - ); - } - /// Returns a best-effort guess of the set of HTLCs that will be present /// on the next local or remote commitment. We cannot be certain as the /// actual set of HTLCs present on the next commitment depends on the @@ -4777,7 +4871,9 @@ impl ChannelContext { /// will *not* be present on the next commitment from `next_commitment_htlcs`, and /// check if their outcome is successful. If it is, we add the value of this claimed /// HTLC to the balance of the claimer. - fn get_next_commitment_value_to_self_msat(&self, local: bool, funding: &FundingScope) -> u64 { + fn get_next_commitment_value_to_self_msat( + &self, local: bool, funding: &FundingScope

, + ) -> u64 { use InboundHTLCRemovalReason::Fulfill; use OutboundHTLCOutcome::Success; @@ -4810,7 +4906,9 @@ impl ChannelContext { .saturating_add(inbound_claimed_htlc_msat) } - fn get_channel_constraints(&self, funding: &FundingScope) -> ChannelConstraints { + fn get_channel_constraints( + &self, funding: &FundingScope

, + ) -> ChannelConstraints { ChannelConstraints { holder_dust_limit_satoshis: self.holder_dust_limit_satoshis, counterparty_selected_channel_reserve_satoshis: funding @@ -4826,8 +4924,8 @@ impl ChannelContext { } } - fn get_next_local_commitment_stats( - &self, funding: &FundingScope, htlc_candidate: Option, + fn get_next_local_commitment_stats( + &self, funding: &FundingScope

, htlc_candidate: Option, include_counterparty_unknown_htlcs: bool, addl_nondust_htlc_count: usize, feerate_per_kw: u32, dust_exposure_limiting_feerate: Option, ) -> Result<(ChannelStats, Vec), ()> { @@ -4893,8 +4991,8 @@ impl ChannelContext { Ok((local_stats, next_commitment_htlcs)) } - fn get_next_remote_commitment_stats( - &self, funding: &FundingScope, htlc_candidate: Option, + fn get_next_remote_commitment_stats( + &self, funding: &FundingScope

, htlc_candidate: Option, include_counterparty_unknown_htlcs: bool, addl_nondust_htlc_count: usize, feerate_per_kw: u32, dust_exposure_limiting_feerate: Option, ) -> Result<(ChannelStats, Vec), ()> { @@ -4960,8 +5058,8 @@ impl ChannelContext { Ok((remote_stats, next_commitment_htlcs)) } - fn validate_update_add_htlc( - &self, funding: &FundingScope, msg: &msgs::UpdateAddHTLC, + fn validate_update_add_htlc( + &self, funding: &FundingScope

, msg: &msgs::UpdateAddHTLC, fee_estimator: &LowerBoundedFeeEstimator, ) -> Result<(), ChannelError> { if msg.amount_msat > funding.get_value_satoshis() * 1000 { @@ -5058,8 +5156,8 @@ impl ChannelContext { Ok(()) } - fn validate_update_fee( - &self, funding: &FundingScope, fee_estimator: &LowerBoundedFeeEstimator, + fn validate_update_fee( + &self, funding: &FundingScope

, fee_estimator: &LowerBoundedFeeEstimator, new_feerate_per_kw: u32, ) -> Result<(), ChannelError> { // Check that we won't be pushed over our dust exposure limit by the feerate increase. @@ -5127,8 +5225,9 @@ impl ChannelContext { } fn validate_commitment_signed( - &self, funding: &FundingScope, transaction_number: u64, commitment_point: PublicKey, - msg: &msgs::CommitmentSigned, fee_estimator: &LowerBoundedFeeEstimator, logger: &L, + &self, funding: &FundingScope, transaction_number: u64, + commitment_point: PublicKey, msg: &msgs::CommitmentSigned, + fee_estimator: &LowerBoundedFeeEstimator, logger: &L, ) -> Result< (HolderCommitmentTransaction, Vec<(HTLCOutputInCommitment, Option<&HTLCSource>)>), ChannelError, @@ -5254,8 +5353,8 @@ impl ChannelContext { Ok((holder_commitment_tx, commitment_data.htlcs_included)) } - fn can_send_update_fee( - &self, funding: &FundingScope, feerate_per_kw: u32, + fn can_send_update_fee( + &self, funding: &FundingScope

, feerate_per_kw: u32, fee_estimator: &LowerBoundedFeeEstimator, logger: &L, ) -> bool { // Before proposing a feerate update, check that we can actually afford the new fee. @@ -5332,8 +5431,8 @@ impl ChannelContext { return true; } - fn can_accept_incoming_htlc( - &self, funding: &FundingScope, dust_exposure_limiting_feerate: Option, logger: &L, + fn can_accept_incoming_htlc( + &self, funding: &FundingScope

, dust_exposure_limiting_feerate: Option, logger: &L, ) -> Result<(), LocalHTLCFailureReason> { // The fee spike buffer (an additional nondust HTLC) we keep for the remote if the channel // is not zero fee. This deviates from the spec because the fee spike buffer requirement @@ -5450,7 +5549,7 @@ impl ChannelContext { #[inline] #[rustfmt::skip] - fn get_commitment_feerate(&self, funding: &FundingScope, generated_by_local: bool) -> u32 { + fn get_commitment_feerate(&self, funding: &FundingScope

, generated_by_local: bool) -> u32 { let mut feerate_per_kw = self.feerate_per_kw; if let Some((feerate, update_state)) = self.pending_update_fee { if match update_state { @@ -5482,7 +5581,7 @@ impl ChannelContext { /// which peer generated this transaction and "to whom" this transaction flows. #[inline] #[rustfmt::skip] - fn build_commitment_transaction(&self, funding: &FundingScope, commitment_number: u64, per_commitment_point: &PublicKey, local: bool, generated_by_local: bool, logger: &L) -> CommitmentData<'_> { + fn build_commitment_transaction(&self, funding: &FundingScope, commitment_number: u64, per_commitment_point: &PublicKey, local: bool, generated_by_local: bool, logger: &L) -> CommitmentData<'_> { let broadcaster_dust_limit_sat = if local { self.holder_dust_limit_satoshis } else { self.counterparty_dust_limit_satoshis }; let feerate_per_kw = self.get_commitment_feerate(funding, generated_by_local); @@ -5491,9 +5590,10 @@ impl ChannelContext { let mut value_to_self_claimed_msat = 0; let mut value_to_remote_claimed_msat = 0; + let channel_params = &funding.channel_transaction_parameters; log_trace!(logger, "Building commitment transaction number {} (really {} xor {}) for channel {} for {}, generated by {} with fee {}...", commitment_number, (INITIAL_COMMITMENT_NUMBER - commitment_number), - get_commitment_transaction_number_obscure_factor(&funding.get_holder_pubkeys().payment_point, &funding.get_counterparty_pubkeys().payment_point, funding.is_outbound()), + get_commitment_transaction_number_obscure_factor(&channel_params.holder_pubkeys.payment_point, &channel_params.counterparty_parameters.pubkeys.payment_point, channel_params.is_outbound_from_holder), self.channel_id, if local { "us" } else { "remote" }, if generated_by_local { "us" } else { "remote" }, feerate_per_kw); @@ -5666,7 +5766,7 @@ impl ChannelContext { /// Returns information on all pending inbound HTLCs. #[rustfmt::skip] - pub fn get_pending_inbound_htlc_details(&self, funding: &FundingScope) -> Vec { + pub fn get_pending_inbound_htlc_details(&self, funding: &FundingScope

) -> Vec { let mut holding_cell_states = new_hash_map(); for holding_cell_update in self.holding_cell_htlc_updates.iter() { match holding_cell_update { @@ -5716,7 +5816,7 @@ impl ChannelContext { /// Returns information on all pending outbound HTLCs. #[rustfmt::skip] - pub fn get_pending_outbound_htlc_details(&self, funding: &FundingScope) -> Vec { + pub fn get_pending_outbound_htlc_details(&self, funding: &FundingScope

) -> Vec { let mut outbound_details = Vec::new(); let dust_buffer_feerate = self.get_dust_buffer_feerate(None); @@ -5757,8 +5857,8 @@ impl ChannelContext { outbound_details } - fn get_available_balances_for_scope( - &self, funding: &FundingScope, fee_estimator: &LowerBoundedFeeEstimator, + fn get_available_balances_for_scope( + &self, funding: &FundingScope

, fee_estimator: &LowerBoundedFeeEstimator, ) -> Result { let htlc_candidate = None; let include_counterparty_unknown_htlcs = true; @@ -5827,15 +5927,19 @@ impl ChannelContext { /// /// Note that if [`Self::is_manual_broadcast`] is true the transaction will be a dummy /// transaction. - pub fn unbroadcasted_funding(&self, funding: &FundingScope) -> Option { + pub fn unbroadcasted_funding( + &self, funding: &FundingScope

, + ) -> Option { self.if_unbroadcasted_funding(|| funding.funding_transaction.clone()) } /// Returns the transaction ID if there is a pending funding transaction that is yet to be /// broadcast. - pub fn unbroadcasted_funding_txid(&self, funding: &FundingScope) -> Option { + pub fn unbroadcasted_funding_txid( + &self, funding: &FundingScope

, + ) -> Option { self.if_unbroadcasted_funding(|| { - funding.channel_transaction_parameters.funding_outpoint.map(|txo| txo.txid) + funding.channel_transaction_parameters.funding_outpoint().map(|txo| txo.txid) }) } @@ -5846,14 +5950,16 @@ impl ChannelContext { /// Returns the transaction ID if there is a pending batch funding transaction that is yet to be /// broadcast. - pub fn unbroadcasted_batch_funding_txid(&self, funding: &FundingScope) -> Option { + pub fn unbroadcasted_batch_funding_txid( + &self, funding: &FundingScope

, + ) -> Option { self.unbroadcasted_funding_txid(funding).filter(|_| self.is_batch_funding()) } /// Shuts down this Channel (no more calls into this Channel may be made afterwards except /// those explicitly stated to be alowed after shutdown, e.g. some simple getters). - fn force_shutdown( - &mut self, funding: &FundingScope, mut closure_reason: ClosureReason, + fn force_shutdown( + &mut self, funding: &FundingScope

, mut closure_reason: ClosureReason, ) -> ShutdownResult { // Note that we MUST only generate a monitor update that indicates force-closure - we're // called during initialization prior to the chain_monitor in the encompassing ChannelManager @@ -5956,23 +6062,116 @@ impl ChannelContext { *broadcasted_latest_txn = Some(broadcast); } - self.channel_state = ChannelState::ShutdownComplete; - self.update_time_counter += 1; - ShutdownResult { - closure_reason, - monitor_update, - dropped_outbound_htlcs, - unbroadcasted_batch_funding_txid, - channel_id: self.channel_id, - user_channel_id: self.user_id, - channel_capacity_satoshis: funding.get_value_satoshis(), - counterparty_node_id: self.counterparty_node_id, - unbroadcasted_funding_tx, - is_manual_broadcast: self.is_manual_broadcast, - channel_funding_txo: funding.get_funding_txo(), - last_local_balance_msat: funding.value_to_self_msat, - splice_funding_failed: None, + self.channel_state = ChannelState::ShutdownComplete; + self.update_time_counter += 1; + ShutdownResult { + closure_reason, + monitor_update, + dropped_outbound_htlcs, + unbroadcasted_batch_funding_txid, + channel_id: self.channel_id, + user_channel_id: self.user_id, + channel_capacity_satoshis: funding.get_value_satoshis(), + counterparty_node_id: self.counterparty_node_id, + unbroadcasted_funding_tx, + is_manual_broadcast: self.is_manual_broadcast, + channel_funding_txo: funding.get_funding_txo(), + last_local_balance_msat: funding.value_to_self_msat, + splice_funding_failed: None, + } + } + + #[rustfmt::skip] + fn initial_commitment_signed( + &mut self, funding: &FundingScope, + received_msg: &str, + channel_id: ChannelId, counterparty_signature: Signature, + holder_commitment_point: &mut HolderCommitmentPoint, + best_block: BestBlock, signer_provider: &SP, logger: &L, + ) -> Result<(ChannelMonitor, CommitmentTransaction), ChannelError> { + // Check counterparty commitment signature (inlined from former check_counterparty_commitment_signature) + let funding_script = funding.get_funding_redeemscript(); + + let commitment_data = self.build_commitment_transaction(funding, + holder_commitment_point.next_transaction_number(), &holder_commitment_point.next_point(), + true, false, logger); + let initial_commitment_tx = commitment_data.tx; + let trusted_tx = initial_commitment_tx.trust(); + let initial_commitment_bitcoin_tx = trusted_tx.built_transaction(); + let sighash = initial_commitment_bitcoin_tx.get_sighash_all(&funding_script, funding.get_value_satoshis()); + // They sign the holder commitment transaction... + log_trace!(logger, "Checking {} tx signature {} by key {} against tx {} (sighash {}) with redeemscript {} for channel {}.", + received_msg, log_bytes!(counterparty_signature.serialize_compact()[..]), log_bytes!(funding.counterparty_funding_pubkey().serialize()), + encode::serialize_hex(&initial_commitment_bitcoin_tx.transaction), log_bytes!(sighash[..]), + encode::serialize_hex(&funding_script), &self.channel_id()); + secp_check!(self.secp_ctx.verify_ecdsa(&sighash, &counterparty_signature, funding.counterparty_funding_pubkey()), format!("Invalid {} signature from peer", received_msg)); + + let commitment_data = self.build_commitment_transaction(funding, + self.counterparty_next_commitment_transaction_number, + &self.counterparty_next_commitment_point.unwrap(), false, false, logger); + let counterparty_initial_commitment_tx = commitment_data.tx; + let counterparty_trusted_tx = counterparty_initial_commitment_tx.trust(); + let counterparty_initial_bitcoin_tx = counterparty_trusted_tx.built_transaction(); + + log_trace!(logger, "Initial counterparty tx for channel {} is: txid {} tx {}", + &self.channel_id(), counterparty_initial_bitcoin_tx.txid, encode::serialize_hex(&counterparty_initial_bitcoin_tx.transaction)); + + let holder_commitment_tx = HolderCommitmentTransaction::new( + initial_commitment_tx, + counterparty_signature, + Vec::new(), + &funding.get_holder_pubkeys().funding_pubkey, + &funding.counterparty_funding_pubkey() + ); + + if self.holder_signer.as_ref().validate_holder_commitment(&holder_commitment_tx, Vec::new()).is_err() { + return Err(ChannelError::close("Failed to validate our commitment".to_owned())); + } + + // Now that we're past error-generating stuff, update our local state: + + let is_v2_established = channel_id.is_v2_channel_id( + &funding.channel_transaction_parameters.holder_pubkeys.revocation_basepoint, + &funding.channel_transaction_parameters.counterparty_parameters.pubkeys.revocation_basepoint, + ); + self.channel_id = channel_id; + + assert!(!self.channel_state.is_monitor_update_in_progress()); // We have not had any monitor(s) yet to fail update! + if !is_v2_established { + if self.is_batch_funding() { + self.channel_state = ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::WAITING_FOR_BATCH); + } else { + self.channel_state = ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::new()); + } + } + if holder_commitment_point.advance(&self.holder_signer, &self.secp_ctx, logger).is_err() { + // We only fail to advance our commitment point/number if we're currently + // waiting for our signer to unblock and provide a commitment point. + // We cannot send accept_channel/open_channel before this has occurred, so if we + // err here by the time we receive funding_created/funding_signed, something has gone wrong. + debug_assert!(false, "We should be ready to advance our commitment point by the time we receive {}", received_msg); + return Err(ChannelError::close("Failed to advance holder commitment point".to_owned())); } + + let obscure_factor = get_commitment_transaction_number_obscure_factor(&funding.get_holder_pubkeys().payment_point, &funding.get_counterparty_pubkeys().payment_point, funding.is_outbound()); + let shutdown_script = self.shutdown_scriptpubkey.clone().map(|script| script.into_inner()); + let monitor_signer = signer_provider.derive_channel_signer(self.channel_keys_id); + // TODO(RBF): When implementing RBF, the funding_txo passed here must only update + // ChannelMonitorImp::first_confirmed_funding_txo during channel establishment, not splicing + let channel_monitor = ChannelMonitor::new( + self.secp_ctx.clone(), monitor_signer, shutdown_script, + funding.get_holder_selected_contest_delay(), &self.destination_script, + &funding.channel_transaction_parameters, funding.is_outbound(), obscure_factor, + holder_commitment_tx, best_block, self.counterparty_node_id, self.channel_id(), + self.is_manual_broadcast, + ); + channel_monitor.provide_initial_counterparty_commitment_tx( + counterparty_initial_commitment_tx.clone(), + ); + + self.counterparty_next_commitment_transaction_number -= 1; + + Ok((channel_monitor, counterparty_initial_commitment_tx)) } /// Only allowed after [`FundingScope::channel_transaction_parameters`] is set. @@ -6018,7 +6217,7 @@ impl ChannelContext { /// downgrade of channel features would be possible so that we can still open the channel. #[rustfmt::skip] pub(crate) fn maybe_downgrade_channel_features( - &mut self, funding: &mut FundingScope, fee_estimator: &LowerBoundedFeeEstimator, + &mut self, funding: &mut FundingScope, fee_estimator: &LowerBoundedFeeEstimator, user_config: &UserConfig, their_features: &InitFeatures, ) -> Result<(), ()> { if !funding.is_outbound() || @@ -6083,7 +6282,7 @@ impl ChannelContext { } fn get_initial_counterparty_commitment_signatures( - &self, funding: &FundingScope, logger: &L, + &self, funding: &FundingScope, logger: &L, ) -> Option<(Signature, Vec)> { let mut commitment_number = self.counterparty_next_commitment_transaction_number; let mut commitment_point = self.counterparty_next_commitment_point.unwrap(); @@ -6105,18 +6304,15 @@ impl ChannelContext { let counterparty_initial_commitment_tx = commitment_data.tx; match self.holder_signer { // TODO (taproot|arik): move match into calling method for Taproot - ChannelSignerType::Ecdsa(ref ecdsa) => { - let channel_parameters = &funding.channel_transaction_parameters; - ecdsa - .sign_counterparty_commitment( - channel_parameters, - &counterparty_initial_commitment_tx, - Vec::new(), - Vec::new(), - &self.secp_ctx, - ) - .ok() - }, + ChannelSignerType::Ecdsa(ref ecdsa) => ecdsa + .sign_counterparty_commitment( + &funding.channel_transaction_parameters, + &counterparty_initial_commitment_tx, + Vec::new(), + Vec::new(), + &self.secp_ctx, + ) + .ok(), // TODO (taproot|arik) #[cfg(taproot)] _ => todo!(), @@ -6124,7 +6320,7 @@ impl ChannelContext { } fn get_initial_commitment_signed_v2( - &mut self, funding: &FundingScope, logger: &L, + &mut self, funding: &FundingScope, logger: &L, ) -> Option { let signatures = self.get_initial_counterparty_commitment_signatures(funding, logger); if let Some((signature, htlc_signatures)) = signatures { @@ -6138,7 +6334,7 @@ impl ChannelContext { channel_id: self.channel_id, htlc_signatures, signature, - funding_txid: funding.get_funding_txo().map(|funding_txo| funding_txo.txid), + funding_txid: Some(funding.channel_transaction_parameters.funding_outpoint.txid), #[cfg(taproot)] partial_signature_with_nonce: None, }) @@ -6152,7 +6348,9 @@ impl ChannelContext { } } - fn check_funding_meets_minimum_depth(&self, funding: &FundingScope, height: u32) -> bool { + fn check_funding_meets_minimum_depth( + &self, funding: &FundingScope

, height: u32, + ) -> bool { let minimum_depth = self .minimum_depth(funding) .expect("ChannelContext::minimum_depth should be set for FundedChannel"); @@ -6176,9 +6374,11 @@ impl ChannelContext { } #[rustfmt::skip] - fn check_for_funding_tx_confirmed( - &mut self, funding: &mut FundingScope, block_hash: &BlockHash, height: u32, - index_in_block: usize, tx: &mut ConfirmedTransaction, logger: &L, + fn check_for_funding_tx_confirmed( + &mut self, + funding: &mut FundingScope

, + block_hash: &BlockHash, height: u32, index_in_block: usize, + tx: &mut ConfirmedTransaction, logger: &L, ) -> Result { let funding_txo = match funding.get_funding_txo() { Some(funding_txo) => funding_txo, @@ -6342,13 +6542,17 @@ pub(super) struct FundingNegotiationContext { impl FundingNegotiationContext { /// Prepare and start interactive transaction negotiation. /// If error occurs, it is caused by our side, not the counterparty. - fn into_interactive_tx_constructor( - self, context: &ChannelContext, funding: &FundingScope, entropy_source: &ES, + fn into_interactive_tx_constructor< + P: ChannelTransactionParametersAccess, + SP: SignerProvider, + ES: EntropySource, + >( + self, context: &ChannelContext, funding: &FundingScope

, entropy_source: &ES, holder_node_id: PublicKey, ) -> (InteractiveTxConstructor, Option) { debug_assert_eq!( self.shared_funding_input.is_some(), - funding.channel_transaction_parameters.splice_parent_funding_txid.is_some(), + funding.channel_transaction_parameters.splice_parent_funding_txid().is_some(), ); if self.shared_funding_input.is_some() { @@ -6408,7 +6612,7 @@ impl FundingNegotiationContext { // Counterparty designates channel data owned by the another channel participant entity. #[cfg_attr(test, derive(Debug))] pub(super) struct FundedChannel { - pub funding: FundingScope, + pub funding: FundingScope, pub context: ChannelContext, holder_commitment_point: HolderCommitmentPoint, @@ -6570,14 +6774,10 @@ macro_rules! maybe_create_splice_funding_failed { .and_then(|pending_splice| pending_splice.funding_negotiation.$get()) .filter(|funding_negotiation| funding_negotiation.is_initiator()) .map(|funding_negotiation| { - let funding_txo = funding_negotiation - .as_funding() - .and_then(|funding| funding.get_funding_txo()) - .map(|txo| txo.into_bitcoin_outpoint()); + let funding_txo = + funding_negotiation.get_funding_txo().map(|txo| txo.into_bitcoin_outpoint()); - let channel_type = funding_negotiation - .as_funding() - .map(|funding| funding.get_channel_type().clone()); + let channel_type = funding_negotiation.get_channel_type().cloned(); let (contributed_inputs, contributed_outputs) = match funding_negotiation { FundingNegotiation::AwaitingAck { context, .. } => { @@ -6620,6 +6820,14 @@ where &self.context } + fn is_v2_established(&self) -> bool { + let channel_parameters = &self.funding.channel_transaction_parameters; + self.context.channel_id().is_v2_channel_id( + &channel_parameters.holder_pubkeys.revocation_basepoint, + &channel_parameters.counterparty_parameters.pubkeys.revocation_basepoint, + ) + } + pub fn force_shutdown(&mut self, closure_reason: ClosureReason) -> ShutdownResult { let splice_funding_failed = self.maybe_fail_splice_negotiation(); @@ -6677,7 +6885,7 @@ where }) } - fn pending_funding(&self) -> &[FundingScope] { + fn pending_funding(&self) -> &[FundingScope] { if let Some(pending_splice) = &self.pending_splice { pending_splice.negotiated_candidates.as_slice() } else { @@ -6685,7 +6893,9 @@ where } } - fn funding_and_pending_funding_iter_mut(&mut self) -> impl Iterator { + fn funding_and_pending_funding_iter_mut( + &mut self, + ) -> impl Iterator> { core::iter::once(&mut self.funding).chain( self.pending_splice .as_mut() @@ -6924,7 +7134,7 @@ where } pub fn funding_outpoint(&self) -> OutPoint { - self.funding.channel_transaction_parameters.funding_outpoint.unwrap() + self.funding.channel_transaction_parameters.funding_outpoint } /// Claims an HTLC while we're disconnected from a peer, dropping the [`ChannelMonitorUpdate`] @@ -7269,31 +7479,6 @@ where self.context.channel_state.clear_waiting_for_batch(); } - /// Unsets the existing funding information for V1 funded channels. - /// - /// This must only be used if the channel has not yet completed funding and has not been used. - /// - /// Further, the channel must be immediately shut down after this with a call to - /// [`ChannelContext::force_shutdown`]. - pub fn unset_funding_info(&mut self) { - let sent_or_received_tx_signatures = self - .context - .interactive_tx_signing_session - .as_ref() - .map(|signing_session| { - signing_session.holder_tx_signatures().is_some() - || signing_session.has_received_tx_signatures() - }) - .unwrap_or(false); - debug_assert!( - matches!( - self.context.channel_state, - ChannelState::FundingNegotiated(_) if !sent_or_received_tx_signatures - ) || matches!(self.context.channel_state, ChannelState::AwaitingChannelReady(_)) - ); - self.context.unset_funding_info(&mut self.funding); - } - /// Handles a channel_ready message from our peer. If we've already sent our channel_ready /// and the channel is now usable (and public), this may generate an announcement_signatures to /// reply with. @@ -7691,7 +7876,9 @@ where "initial commitment_signed", ); - let (channel_monitor, _) = self.initial_commitment_signed( + let (channel_monitor, _) = self.context.initial_commitment_signed( + &self.funding, + "commitment_signed", self.context.channel_id(), msg.signature, holder_commitment_point, @@ -7925,8 +8112,7 @@ where let mut commitment_txs = Vec::with_capacity(self.pending_funding().len() + 1); let mut htlc_data = None; for funding in core::iter::once(&self.funding).chain(self.pending_funding().iter()) { - let funding_txid = - funding.get_funding_txid().expect("Funding txid must be known for pending scope"); + let funding_txid = funding.get_funding_txid(); let msg = messages.get(&funding_txid).ok_or_else(|| { ChannelError::close(format!( "Peer did not send a commitment_signed for pending splice transaction: {}", @@ -9721,7 +9907,7 @@ where // - MUST retransmit `announcement_signatures`. if let Some(funding_locked) = &msg.my_current_funding_locked { if funding_locked.should_retransmit(msgs::FundingLockedFlags::AnnouncementSignatures) { - if self.funding.get_funding_txid() == Some(funding_locked.txid) { + if self.funding.get_funding_txid() == funding_locked.txid { self.context.announcement_sigs_state = AnnouncementSigsState::NotSent; } } @@ -9803,7 +9989,7 @@ where } }) .or_else(|| Some(&self.funding)) - .filter(|funding| funding.get_funding_txid() == Some(funding_txid)) + .filter(|funding| funding.get_funding_txid() == funding_txid) .ok_or_else(|| { let message = "Failed to find funding for new commitment_signed".to_owned(); ChannelError::Close( @@ -9918,7 +10104,7 @@ where let inferred_splice_locked = msg.my_current_funding_locked.as_ref().and_then(|funding_locked| { self.pending_funding() .iter() - .find(|funding| funding.get_funding_txid() == Some(funding_locked.txid)) + .find(|funding| funding.get_funding_txid() == funding_locked.txid) .and_then(|_| { self.pending_splice.as_ref().and_then(|pending_splice| { (Some(funding_locked.txid) != pending_splice.received_funding_txid) @@ -10964,7 +11150,9 @@ where } } - fn check_funding_meets_minimum_depth(&self, funding: &FundingScope, height: u32) -> bool { + fn check_funding_meets_minimum_depth( + &self, funding: &FundingScope

, height: u32, + ) -> bool { self.context.check_funding_meets_minimum_depth(funding, height) } @@ -11007,7 +11195,7 @@ where let funding = pending_splice .negotiated_candidates .iter_mut() - .find(|funding| funding.get_funding_txid() == Some(splice_txid)) + .find(|funding| funding.get_funding_txid() == splice_txid) .unwrap(); let prev_funding_txid = self.funding.get_funding_txid(); @@ -11292,7 +11480,7 @@ where if funding.get_funding_tx_confirmations(height) == 0 { funding.funding_tx_confirmation_height = 0; if let Some(sent_funding_txid) = pending_splice.sent_funding_txid { - if Some(sent_funding_txid) == funding.get_funding_txid() { + if sent_funding_txid == funding.get_funding_txid() { log_warn!( logger, "Unconfirming sent splice_locked txid {} for channel {}", @@ -11341,24 +11529,11 @@ where } pub fn get_relevant_txids(&self) -> impl Iterator)> + '_ { - core::iter::once(&self.funding) - .chain(self.pending_funding().iter()) - .map(|funding| { - ( - funding.get_funding_txid(), - funding.get_funding_tx_confirmation_height(), - funding.funding_tx_confirmed_in, - ) - }) - .filter_map(|(txid_opt, height_opt, hash_opt)| { - if let (Some(funding_txid), Some(conf_height), Some(block_hash)) = - (txid_opt, height_opt, hash_opt) - { - Some((funding_txid, conf_height, Some(block_hash))) - } else { - None - } - }) + core::iter::once(&self.funding).chain(self.pending_funding().iter()).filter_map(|funding| { + let conf_height = funding.get_funding_tx_confirmation_height()?; + let block_hash = funding.funding_tx_confirmed_in?; + Some((funding.get_funding_txid(), conf_height, Some(block_hash))) + }) } /// Checks if any funding transaction is no longer confirmed in the main chain. This may @@ -11371,7 +11546,7 @@ where ) -> Result<(), ClosureReason> { let unconfirmed_funding = self .funding_and_pending_funding_iter_mut() - .find(|funding| funding.get_funding_txid() == Some(*txid)); + .find(|funding| funding.get_funding_txid() == *txid); if let Some(funding) = unconfirmed_funding { if funding.funding_tx_confirmation_height != 0 { @@ -11628,9 +11803,7 @@ where self.pending_splice .as_ref() .and_then(|pending_splice| pending_splice.sent_funding_txid) - .or_else(|| { - self.is_our_channel_ready().then(|| self.funding.get_funding_txid()).flatten() - }) + .or_else(|| self.is_our_channel_ready().then(|| self.funding.get_funding_txid())) .map(|txid| { let mut funding_locked = msgs::FundingLocked { txid, retransmit_flags: 0 }; @@ -11641,7 +11814,7 @@ where // - otherwise: // - MUST set the `announcement_signatures` bit to `0` in `retransmit_flags`. if self.context.config.announce_for_forwarding { - if self.funding.get_funding_txid() != Some(txid) + if self.funding.get_funding_txid() != txid || self.context.announcement_sigs.is_none() { funding_locked.retransmit(msgs::FundingLockedFlags::AnnouncementSignatures); @@ -11875,12 +12048,8 @@ where debug_assert!(self.pending_splice.is_none()); // Rotate the funding pubkey using the prev_funding_txid as a tweak let prev_funding_txid = self.funding.get_funding_txid(); - let funding_pubkey = match (prev_funding_txid, &self.context.holder_signer) { - (None, _) => { - debug_assert!(false); - self.funding.get_holder_pubkeys().funding_pubkey - }, - (Some(prev_funding_txid), ChannelSignerType::Ecdsa(ecdsa)) => { + let funding_pubkey = match &self.context.holder_signer { + ChannelSignerType::Ecdsa(ecdsa) => { ecdsa.new_funding_pubkey(prev_funding_txid, &self.context.secp_ctx) }, #[cfg(taproot)] @@ -11939,7 +12108,7 @@ where /// Checks during handling splice_init pub fn validate_splice_init( &self, msg: &msgs::SpliceInit, our_funding_contribution: SignedAmount, - ) -> Result { + ) -> Result, ChannelError> { if self.holder_commitment_point.current_point().is_none() { return Err(ChannelError::WarnAndDisconnect(format!( "Channel {} commitment point needs to be advanced once before spliced", @@ -11981,12 +12150,8 @@ where // Rotate the pubkeys using the prev_funding_txid as a tweak let prev_funding_txid = self.funding.get_funding_txid(); - let funding_pubkey = match (prev_funding_txid, &self.context.holder_signer) { - (None, _) => { - debug_assert!(false); - self.funding.get_holder_pubkeys().funding_pubkey - }, - (Some(prev_funding_txid), ChannelSignerType::Ecdsa(ecdsa)) => { + let funding_pubkey = match &self.context.holder_signer { + ChannelSignerType::Ecdsa(ecdsa) => { ecdsa.new_funding_pubkey(prev_funding_txid, &self.context.secp_ctx) }, #[cfg(taproot)] @@ -12230,7 +12395,9 @@ where Ok(tx_msg_opt) } - fn validate_splice_ack(&self, msg: &msgs::SpliceAck) -> Result { + fn validate_splice_ack( + &self, msg: &msgs::SpliceAck, + ) -> Result, ChannelError> { // TODO(splicing): Add check that we are the splice (quiescence) initiator let pending_splice = self @@ -12275,8 +12442,8 @@ where )) } - fn get_holder_counterparty_balances_floor_incl_fee( - &self, funding: &FundingScope, + fn get_holder_counterparty_balances_floor_incl_fee( + &self, funding: &FundingScope

, ) -> Result<(Amount, Amount), String> { let include_counterparty_unknown_htlcs = true; // Make sure that that the funder of the channel can pay the transaction fees for an additional @@ -12350,7 +12517,7 @@ where if !pending_splice .negotiated_candidates .iter() - .any(|funding| funding.get_funding_txid() == Some(msg.splice_txid)) + .any(|funding| funding.get_funding_txid() == msg.splice_txid) { let err = "unknown splice funding txid"; return Err(ChannelError::close(err.to_string())); @@ -12682,10 +12849,11 @@ where #[rustfmt::skip] fn build_commitment_no_state_update( - &self, funding: &FundingScope, logger: &L, + &self, funding: &FundingScope, logger: &L, ) -> (Vec<(HTLCOutputInCommitment, Option<&HTLCSource>)>, CommitmentTransaction) { let commitment_data = self.context.build_commitment_transaction( - funding, self.context.counterparty_next_commitment_transaction_number, + funding, + self.context.counterparty_next_commitment_transaction_number, &self.context.counterparty_next_commitment_point.unwrap(), false, true, logger, ); let counterparty_commitment_tx = commitment_data.tx; @@ -12706,14 +12874,15 @@ where #[rustfmt::skip] fn send_commitment_no_state_update_for_funding( - &self, funding: &FundingScope, logger: &L, + &self, funding: &FundingScope, logger: &L, ) -> Result { // Get the fee tests from `build_commitment_no_state_update` #[cfg(any(test, fuzzing))] self.build_commitment_no_state_update(funding, logger); let commitment_data = self.context.build_commitment_transaction( - funding, self.context.counterparty_next_commitment_transaction_number, + funding, + self.context.counterparty_next_commitment_transaction_number, &self.context.counterparty_next_commitment_point.unwrap(), false, true, logger, ); let counterparty_commitment_tx = commitment_data.tx; @@ -13246,7 +13415,7 @@ where /// A not-yet-funded outbound (from holder) channel using V1 channel establishment. pub(super) struct OutboundV1Channel { - pub funding: FundingScope, + pub funding: FundingScope, pub context: ChannelContext, pub unfunded_context: UnfundedChannelContext, /// We tried to send an `open_channel` message but our commitment point wasn't ready. @@ -13311,45 +13480,6 @@ impl OutboundV1Channel { Ok(chan) } - /// Only allowed after [`FundingScope::channel_transaction_parameters`] is set. - #[rustfmt::skip] - fn get_funding_created_msg(&mut self, logger: &L) -> Option { - let commitment_data = self.context.build_commitment_transaction(&self.funding, - self.context.counterparty_next_commitment_transaction_number, - &self.context.counterparty_next_commitment_point.unwrap(), false, false, logger); - let counterparty_initial_commitment_tx = commitment_data.tx; - let signature = match &self.context.holder_signer { - // TODO (taproot|arik): move match into calling method for Taproot - ChannelSignerType::Ecdsa(ecdsa) => { - let channel_parameters = &self.funding.channel_transaction_parameters; - ecdsa.sign_counterparty_commitment(channel_parameters, &counterparty_initial_commitment_tx, Vec::new(), Vec::new(), &self.context.secp_ctx) - .map(|(sig, _)| sig).ok() - }, - // TODO (taproot|arik) - #[cfg(taproot)] - _ => todo!() - }; - - if signature.is_some() && self.context.signer_pending_funding { - log_trace!(logger, "Counterparty commitment signature ready for funding_created message: clearing signer_pending_funding"); - self.context.signer_pending_funding = false; - } else if signature.is_none() { - log_trace!(logger, "funding_created awaiting signer; setting signer_pending_funding"); - self.context.signer_pending_funding = true; - }; - - signature.map(|signature| msgs::FundingCreated { - temporary_channel_id: self.context.temporary_channel_id.unwrap(), - funding_txid: self.funding.channel_transaction_parameters.funding_outpoint.as_ref().unwrap().txid, - funding_output_index: self.funding.channel_transaction_parameters.funding_outpoint.as_ref().unwrap().index, - signature, - #[cfg(taproot)] - partial_signature_with_nonce: None, - #[cfg(taproot)] - next_local_nonce: None, - }) - } - /// Updates channel state with knowledge of the funding transaction's txid/index, and generates /// a funding_created message for the remote peer. /// Panics if called at some time other than immediately after initial handshake, if called twice, @@ -13358,8 +13488,8 @@ impl OutboundV1Channel { /// Do NOT broadcast the funding transaction until after a successful funding_signed call! /// If an Err is returned, it is a ChannelError::Close. #[rustfmt::skip] - pub fn get_funding_created(&mut self, funding_transaction: Transaction, funding_txo: OutPoint, is_batch_funding: bool, logger: &L) - -> Result, (Self, ChannelError)> { + pub fn get_funding_created(mut self, funding_transaction: Transaction, funding_txo: OutPoint, is_batch_funding: bool, logger: &L) + -> Result<(PendingV1Channel, Option), (Self, ChannelError)> { if !self.funding.is_outbound() { panic!("Tried to create outbound funding_created message on an inbound channel!"); } @@ -13390,8 +13520,17 @@ impl OutboundV1Channel { self.funding.funding_transaction = Some(funding_transaction); self.context.is_batch_funding = Some(()).filter(|_| is_batch_funding); - let funding_created = self.get_funding_created_msg(logger); - Ok(funding_created) + // Both late-bound fields are set: counterparty_parameters set during handshake, + // funding_outpoint set above. + let funding = self.funding.into_complete() + .expect("counterparty_parameters set during handshake, funding_outpoint set above"); + let mut pending = PendingV1Channel { + funding, + context: self.context, + unfunded_context: self.unfunded_context, + }; + let funding_created = pending.get_funding_created_msg(logger); + Ok((pending, funding_created)) } /// If we receive an error message, it may only be a rejection of the channel type we tried, @@ -13485,6 +13624,96 @@ impl OutboundV1Channel { ) } + /// Indicates that the signer may have some signatures for us, so we should retry if we're + /// blocked. + #[rustfmt::skip] + pub fn signer_maybe_unblocked( + &mut self, chain_hash: ChainHash, logger: &L + ) -> Option { + // If we were pending a commitment point, retry the signer and advance to an + // available state. + if self.unfunded_context.holder_commitment_point.is_none() { + self.unfunded_context.holder_commitment_point = HolderCommitmentPoint::new(&self.context.holder_signer, &self.context.secp_ctx); + } + if let Some(ref mut point) = self.unfunded_context.holder_commitment_point { + if !point.can_advance() { + point.try_resolve_pending(&self.context.holder_signer, &self.context.secp_ctx, logger); + } + } + if self.signer_pending_open_channel { + log_trace!(logger, "Attempting to generate open_channel..."); + self.get_open_channel(chain_hash, logger) + } else { None } + } +} + +/// An outbound channel using V1 channel establishment that has generated a `funding_created` +/// message but has not yet received `funding_signed`. +pub(super) struct PendingV1Channel { + pub funding: FundingScope, + pub context: ChannelContext, + pub unfunded_context: UnfundedChannelContext, +} + +impl PendingV1Channel { + pub fn abandon_unfunded_chan(&mut self, closure_reason: ClosureReason) -> ShutdownResult { + self.context.force_shutdown(&self.funding, closure_reason) + } + + #[rustfmt::skip] + fn get_funding_created_msg(&mut self, logger: &L) -> Option { + let commitment_data = self.context.build_commitment_transaction(&self.funding, + self.context.counterparty_next_commitment_transaction_number, + &self.context.counterparty_next_commitment_point.unwrap(), false, false, logger); + let counterparty_initial_commitment_tx = commitment_data.tx; + let signature = match &self.context.holder_signer { + // TODO (taproot|arik): move match into calling method for Taproot + ChannelSignerType::Ecdsa(ecdsa) => { + ecdsa.sign_counterparty_commitment(&self.funding.channel_transaction_parameters, &counterparty_initial_commitment_tx, Vec::new(), Vec::new(), &self.context.secp_ctx) + .map(|(sig, _)| sig).ok() + }, + // TODO (taproot|arik) + #[cfg(taproot)] + _ => todo!() + }; + + if signature.is_some() && self.context.signer_pending_funding { + log_trace!(logger, "Counterparty commitment signature ready for funding_created message: clearing signer_pending_funding"); + self.context.signer_pending_funding = false; + } else if signature.is_none() { + log_trace!(logger, "funding_created awaiting signer; setting signer_pending_funding"); + self.context.signer_pending_funding = true; + }; + + signature.map(|signature| msgs::FundingCreated { + temporary_channel_id: self.context.temporary_channel_id.unwrap(), + funding_txid: self.funding.channel_transaction_parameters.funding_outpoint.txid, + funding_output_index: self.funding.channel_transaction_parameters.funding_outpoint.index, + signature, + #[cfg(taproot)] + partial_signature_with_nonce: None, + #[cfg(taproot)] + next_local_nonce: None, + }) + } + + /// Indicates that the signer may have some signatures for us, so we should retry if we're + /// blocked. + #[rustfmt::skip] + pub fn signer_maybe_unblocked( + &mut self, logger: &L, + ) -> Option { + if let Some(ref mut point) = self.unfunded_context.holder_commitment_point { + if !point.can_advance() { + point.try_resolve_pending(&self.context.holder_signer, &self.context.secp_ctx, logger); + } + } + if self.context.signer_pending_funding { + log_trace!(logger, "Attempting to generate pending funding created..."); + self.get_funding_created_msg(logger) + } else { None } + } + /// Handles a funding_signed message from the remote end. /// If this call is successful, broadcast the funding transaction (and not before!) pub fn funding_signed( @@ -13492,7 +13721,7 @@ impl OutboundV1Channel { logger: &L, ) -> Result< (FundedChannel, ChannelMonitor), - (OutboundV1Channel, ChannelError), + (PendingV1Channel, ChannelError), > { if !self.funding.is_outbound() { let err = "Received funding_signed for an inbound channel?"; @@ -13514,7 +13743,9 @@ impl OutboundV1Channel { "funding_signed", ); - let (channel_monitor, _) = match self.initial_commitment_signed( + let (channel_monitor, _) = match self.context.initial_commitment_signed( + &self.funding, + "funding_signed", self.context.channel_id(), msg.signature, &mut holder_commitment_point, @@ -13522,8 +13753,10 @@ impl OutboundV1Channel { signer_provider, logger, ) { - Ok(channel_monitor) => channel_monitor, - Err(err) => return Err((self, err)), + Ok(result) => result, + Err(err) => { + return Err((self, err)); + }, }; log_info!( @@ -13554,49 +13787,27 @@ impl OutboundV1Channel { Ok((channel, channel_monitor)) } - /// Indicates that the signer may have some signatures for us, so we should retry if we're - /// blocked. - #[rustfmt::skip] - pub fn signer_maybe_unblocked( - &mut self, chain_hash: ChainHash, logger: &L - ) -> (Option, Option) { - // If we were pending a commitment point, retry the signer and advance to an - // available state. - if self.unfunded_context.holder_commitment_point.is_none() { - self.unfunded_context.holder_commitment_point = HolderCommitmentPoint::new(&self.context.holder_signer, &self.context.secp_ctx); - } - if let Some(ref mut point) = self.unfunded_context.holder_commitment_point { - if !point.can_advance() { - point.try_resolve_pending(&self.context.holder_signer, &self.context.secp_ctx, logger); - } - } - let open_channel = if self.signer_pending_open_channel { - log_trace!(logger, "Attempting to generate open_channel..."); - self.get_open_channel(chain_hash, logger) - } else { None }; - let funding_created = if self.context.signer_pending_funding && self.funding.is_outbound() { - log_trace!(logger, "Attempting to generate pending funding created..."); - self.get_funding_created_msg(logger) - } else { None }; - (open_channel, funding_created) - } - - /// Unsets the existing funding information. + /// Resets the channel ID to the temporary one so that error handling closes the + /// correct channel entry. /// /// The channel must be immediately shut down after this with a call to /// [`ChannelContext::force_shutdown`]. + #[rustfmt::skip] pub fn unset_funding_info(&mut self) { debug_assert!(matches!( self.context.channel_state, ChannelState::FundingNegotiated(_) if self.context.interactive_tx_signing_session.is_none() )); - self.context.unset_funding_info(&mut self.funding); + self.context.channel_id = self.context.temporary_channel_id.expect( + "temporary_channel_id should be set since unset_funding_info is only called on \ + channels that were unfunded immediately beforehand" + ); } } /// A not-yet-funded inbound (from counterparty) channel using V1 channel establishment. pub(super) struct InboundV1Channel { - pub funding: FundingScope, + pub funding: FundingScope, pub context: ChannelContext, pub unfunded_context: UnfundedChannelContext, pub signer_pending_accept_channel: bool, @@ -13804,8 +14015,15 @@ impl InboundV1Channel { let funding_txo = OutPoint { txid: msg.funding_txid, index: msg.funding_output_index }; self.funding.channel_transaction_parameters.funding_outpoint = Some(funding_txo); - let (channel_monitor, counterparty_initial_commitment_tx) = match self - .initial_commitment_signed( + let funding = self + .funding + .into_complete() + .expect("channel_transaction_parameters must be complete for funding_created"); + + let (channel_monitor, counterparty_initial_commitment_tx) = + match self.context.initial_commitment_signed( + &funding, + "funding_created", ChannelId::v1_from_funding_outpoint(funding_txo), msg.signature, &mut holder_commitment_point, @@ -13813,12 +14031,22 @@ impl InboundV1Channel { signer_provider, logger, ) { - Ok(channel_monitor) => channel_monitor, - Err(err) => return Err((self, err)), - }; + Ok(result) => result, + Err(err) => { + return Err(( + InboundV1Channel { + funding: funding.into_partial(), + context: self.context, + unfunded_context: self.unfunded_context, + signer_pending_accept_channel: self.signer_pending_accept_channel, + }, + err, + )); + }, + }; let funding_signed = self.context.get_funding_signed_msg( - &self.funding.channel_transaction_parameters, + &funding.channel_transaction_parameters, logger, counterparty_initial_commitment_tx, ); @@ -13833,7 +14061,7 @@ impl InboundV1Channel { // Promote the channel to a full-fledged one now that we have updated the state and have a // `ChannelMonitor`. let mut channel = FundedChannel { - funding: self.funding, + funding, context: self.context, holder_commitment_point, pending_splice: None, @@ -13875,9 +14103,17 @@ impl InboundV1Channel { } } -// A not-yet-funded channel using V2 channel establishment. +/// A V2 channel that has completed funding transaction construction but has not yet +/// received `commitment_signed`. pub(super) struct PendingV2Channel { - pub funding: FundingScope, + pub funding: FundingScope, + pub context: ChannelContext, + pub unfunded_context: UnfundedChannelContext, +} + +// A not-yet-funded channel using V2 channel establishment. +pub(super) struct UnfundedV2Channel { + pub funding: FundingScope, pub context: ChannelContext, pub unfunded_context: UnfundedChannelContext, pub funding_negotiation_context: FundingNegotiationContext, @@ -13885,7 +14121,7 @@ pub(super) struct PendingV2Channel { pub interactive_tx_constructor: Option, } -impl PendingV2Channel { +impl UnfundedV2Channel { #[allow(dead_code)] // TODO(dual_funding): Remove once creating V2 channels is enabled. #[rustfmt::skip] pub fn new_outbound( @@ -15926,7 +16162,7 @@ mod tests { value: Amount::from_sat(10000000), script_pubkey: output_script.clone(), }]}; let funding_outpoint = OutPoint{ txid: tx.compute_txid(), index: 0 }; - let funding_created_msg = node_a_chan.get_funding_created(tx.clone(), funding_outpoint, false, &&logger).map_err(|_| ()).unwrap(); + let (node_a_chan, funding_created_msg) = node_a_chan.get_funding_created(tx.clone(), funding_outpoint, false, &&logger).map_err(|_| ()).unwrap(); let (_, funding_signed_msg, _) = node_b_chan.funding_created(&funding_created_msg.unwrap(), best_block, &&keys_provider, &&logger).map_err(|_| ()).unwrap(); // Node B --> Node A: funding signed @@ -16069,7 +16305,7 @@ mod tests { value: Amount::from_sat(10000000), script_pubkey: output_script.clone(), }]}; let funding_outpoint = OutPoint{ txid: tx.compute_txid(), index: 0 }; - let funding_created_msg = node_a_chan.get_funding_created(tx.clone(), funding_outpoint, false, &&logger).map_err(|_| ()).unwrap(); + let (node_a_chan, funding_created_msg) = node_a_chan.get_funding_created(tx.clone(), funding_outpoint, false, &&logger).map_err(|_| ()).unwrap(); let (mut node_b_chan, funding_signed_msg, _) = node_b_chan.funding_created(&funding_created_msg.unwrap(), best_block, &&keys_provider, &&logger).map_err(|_| ()).unwrap(); // Node B --> Node A: funding signed @@ -16265,7 +16501,7 @@ mod tests { value: Amount::from_sat(10000000), script_pubkey: output_script.clone(), }]}; let funding_outpoint = OutPoint{ txid: tx.compute_txid(), index: 0 }; - let funding_created_msg = node_a_chan.get_funding_created(tx.clone(), funding_outpoint, false, &&logger).map_err(|_| ()).unwrap(); + let (node_a_chan, funding_created_msg) = node_a_chan.get_funding_created(tx.clone(), funding_outpoint, false, &&logger).map_err(|_| ()).unwrap(); let (_, funding_signed_msg, _) = node_b_chan.funding_created(&funding_created_msg.unwrap(), best_block, &&keys_provider, &&logger).map_err(|_| ()).unwrap(); // Node B --> Node A: funding signed @@ -16374,11 +16610,11 @@ mod tests { }], }; let funding_outpoint = OutPoint { txid: tx.compute_txid(), index: 0 }; - let funding_created = outbound_chan + let (_pending_chan, funding_created) = outbound_chan .get_funding_created(tx.clone(), funding_outpoint, false, &&logger) .map_err(|_| ()) - .unwrap() .unwrap(); + let funding_created = funding_created.unwrap(); let mut chan = match inbound_chan.funding_created( &funding_created, best_block, @@ -18034,7 +18270,7 @@ mod tests { }, ]}; let funding_outpoint = OutPoint{ txid: tx.compute_txid(), index: 0 }; - let funding_created_msg = node_a_chan.get_funding_created( + let (node_a_chan, funding_created_msg) = node_a_chan.get_funding_created( tx.clone(), funding_outpoint, true, &&logger, ).map_err(|_| ()).unwrap(); let (mut node_b_chan, funding_signed_msg, _) = node_b_chan.funding_created( diff --git a/lightning/src/ln/channel_open_tests.rs b/lightning/src/ln/channel_open_tests.rs index 08cabc053c5..c5a5f6749ab 100644 --- a/lightning/src/ln/channel_open_tests.rs +++ b/lightning/src/ln/channel_open_tests.rs @@ -1509,15 +1509,17 @@ pub fn test_duplicate_chan_id() { let chan_id = open_chan_2_msg.common_fields.temporary_channel_id; let mut channel = a_peer_state.channel_by_id.remove(&chan_id).unwrap(); - if let Some(mut chan) = channel.as_unfunded_outbound_v1_mut() { - let logger = test_utils::TestLogger::new(); - chan.get_funding_created(tx.clone(), funding_outpoint, false, &&logger) - .map_err(|_| ()) - .unwrap() - } else { - panic!("Unexpected Channel phase") + match channel.into_unfunded_outbound_v1() { + Ok(chan) => { + let logger = test_utils::TestLogger::new(); + let (_pending, funding_created) = chan + .get_funding_created(tx.clone(), funding_outpoint, false, &&logger) + .map_err(|_| ()) + .unwrap(); + funding_created.unwrap() + }, + Err(_) => panic!("Unexpected Channel phase"), } - .unwrap() }; check_added_monitors(&nodes[0], 0); nodes[1].node.handle_funding_created(node_a_id, &funding_created); diff --git a/lightning/src/ln/channel_state.rs b/lightning/src/ln/channel_state.rs index 5547bee8f4c..901ed94bb52 100644 --- a/lightning/src/ln/channel_state.rs +++ b/lightning/src/ln/channel_state.rs @@ -15,7 +15,8 @@ use bitcoin::secp256k1::PublicKey; use crate::chain::chaininterface::{FeeEstimator, LowerBoundedFeeEstimator}; use crate::chain::transaction::OutPoint; -use crate::ln::channel::Channel; +use crate::ln::chan_utils::ChannelTransactionParametersAccess; +use crate::ln::channel::{AvailableBalances, Channel, ChannelContext, FundingScope}; use crate::ln::types::ChannelId; use crate::sign::SignerProvider; use crate::types::features::{ChannelTypeFeatures, InitFeatures}; @@ -523,18 +524,16 @@ impl ChannelDetails { channel: &Channel, best_block_height: u32, latest_features: InitFeatures, fee_estimator: &LowerBoundedFeeEstimator, ) -> Self { - let context = channel.context(); - let funding = channel.funding(); - let balance_result = channel.get_available_balances(fee_estimator); - let balance = balance_result.unwrap_or_else(|()| { - debug_assert!(false, "some channel balance has been overdrawn"); - crate::ln::channel::AvailableBalances { - inbound_capacity_msat: 0, - outbound_capacity_msat: 0, - next_outbound_htlc_limit_msat: 0, - next_outbound_htlc_minimum_msat: u64::MAX, - } - }); + channel.channel_details(best_block_height, latest_features, fee_estimator) + } + + #[rustfmt::skip] + pub(super) fn from_channel_parts( + context: &ChannelContext, funding: &FundingScope

, + balance: AvailableBalances, minimum_depth: Option, + best_block_height: u32, latest_features: InitFeatures, + _fee_estimator: &LowerBoundedFeeEstimator, + ) -> Self { let (to_remote_reserve_satoshis, to_self_reserve_satoshis) = funding.get_holder_counterparty_selected_channel_reserve_satoshis(); #[allow(deprecated)] // TODO: Remove once balance_msat is removed. @@ -583,7 +582,7 @@ impl ChannelDetails { next_outbound_htlc_limit_msat: balance.next_outbound_htlc_limit_msat, next_outbound_htlc_minimum_msat: balance.next_outbound_htlc_minimum_msat, user_channel_id: context.get_user_id(), - confirmations_required: channel.minimum_depth(), + confirmations_required: minimum_depth, confirmations: Some(funding.get_funding_tx_confirmations(best_block_height)), force_close_spend_delay: funding.get_counterparty_selected_contest_delay(), is_outbound: funding.is_outbound(), diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index ada27af749f..63163dd0c6e 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -54,15 +54,17 @@ use crate::events::{ InboundChannelFunds, PaymentFailureReason, ReplayEvent, }; use crate::events::{FundingInfo, PaidBolt12Invoice}; -use crate::ln::chan_utils::selected_commitment_sat_per_1000_weight; +use crate::ln::chan_utils::{ + selected_commitment_sat_per_1000_weight, ChannelTransactionParametersAccess, +}; #[cfg(any(test, fuzzing, feature = "_test_utils"))] use crate::ln::channel::QuiescentAction; use crate::ln::channel::QuiescentError; use crate::ln::channel::{ self, hold_time_since, Channel, ChannelError, ChannelUpdateStatus, DisconnectResult, FundedChannel, FundingTxSigned, InboundV1Channel, InteractiveTxMsgError, OutboundHop, - OutboundV1Channel, PendingV2Channel, ReconnectionMsg, ShutdownResult, SpliceFundingFailed, - StfuResponse, UpdateFulfillCommitFetch, WithChannelContext, + OutboundV1Channel, ReconnectionMsg, ShutdownResult, SpliceFundingFailed, StfuResponse, + UnfundedV2Channel, UpdateFulfillCommitFetch, WithChannelContext, }; use crate::ln::channel_state::ChannelDetails; use crate::ln::funding::{FundingContribution, FundingTemplate}; @@ -1717,9 +1719,8 @@ impl PeerState { return false; } } - let chan_is_funded_or_outbound = |(_, channel): (_, &Channel)| { - channel.is_funded() || channel.funding().is_outbound() - }; + let chan_is_funded_or_outbound = + |(_, channel): (_, &Channel)| channel.is_funded() || channel.is_outbound(); !self.channel_by_id.iter().any(chan_is_funded_or_outbound) && self.monitor_update_blocked_actions.is_empty() && self.closed_channel_monitor_update_ids.is_empty() @@ -6120,7 +6121,7 @@ impl< let logger = WithChannelContext::from(&self.logger, &chan.context, None); let funding_res = chan.get_funding_created(funding_transaction, funding_txo, is_batch_funding, &&logger); let (mut chan, msg_opt) = match funding_res { - Ok(funding_msg) => (chan, funding_msg), + Ok((pending, funding_msg)) => (pending, funding_msg), Err((mut chan, chan_err)) => { let api_err = APIError::ChannelUnavailable { err: "Signer refused to sign the initial commitment transaction".to_owned() }; return abandon_chan!(chan_err, api_err, chan); @@ -10834,7 +10835,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ }); (*temporary_channel_id, Channel::from(channel), message_send_event) }), - OpenChannelMessage::V2(open_channel_msg) => PendingV2Channel::new_inbound( + OpenChannelMessage::V2(open_channel_msg) => UnfundedV2Channel::new_inbound( &self.fee_estimator, &self.entropy_source, &self.signer_provider, @@ -10898,7 +10899,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ if accept_0conf { // This should have been correctly configured by the call to Inbound(V1/V2)Channel::new. debug_assert!(channel.minimum_depth().unwrap() == 0); - } else if channel.funding().get_channel_type().requires_zero_conf() { + } else if channel.get_channel_type().requires_zero_conf() { let send_msg_err_event = MessageSendEvent::HandleError { node_id: channel.context().get_counterparty_node_id(), action: msgs::ErrorAction::SendErrorMessage { @@ -10998,7 +10999,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ }, None => { // Outbound channels don't contribute to the unfunded count in the DoS context. - if chan.funding().is_outbound() { + if chan.is_outbound() { continue; } @@ -11173,7 +11174,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ let mut peer_state_lock = peer_state_mutex.lock().unwrap(); let peer_state = &mut *peer_state_lock; - let (mut chan, funding_msg_opt, monitor) = match peer_state + let (chan, funding_msg_opt, monitor) = match peer_state .channel_by_id .remove(&msg.temporary_channel_id) .map(Channel::into_unfunded_inbound_v1) @@ -11229,8 +11230,8 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ // immediately, we'll remove the existing channel from `outpoint_to_peer`. // Thus, we must first unset the funding outpoint on the channel. let err = ChannelError::close($err.to_owned()); - chan.unset_funding_info(); let mut chan = Channel::from(chan); + chan.unfund(); return Err(self.locked_handle_unfunded_close(err, &mut chan).1); }}; } @@ -11407,36 +11408,36 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ match peer_state.channel_by_id.entry(msg.channel_id) { hash_map::Entry::Occupied(mut chan_entry) => { let chan = chan_entry.get_mut(); - match chan - .funding_signed(&msg, best_block, &self.signer_provider, &self.logger) - .and_then(|(funded_chan, monitor)| { - self.chain_monitor - .watch_channel(funded_chan.context.channel_id(), monitor) - .map_err(|()| { + match chan.funding_signed(&msg, best_block, &self.signer_provider, &self.logger) { + Ok((funded_chan, monitor)) => { + let channel_id = funded_chan.context.channel_id(); + match self.chain_monitor.watch_channel(channel_id, monitor) { + Ok(persist_status) => { + if let Some(data) = self.handle_initial_monitor( + &mut peer_state.in_flight_monitor_updates, + &mut peer_state.monitor_update_blocked_actions, + &mut peer_state.pending_msg_events, + peer_state.is_connected, + funded_chan, + persist_status, + ) { + mem::drop(peer_state_lock); + mem::drop(per_peer_state); + self.handle_post_monitor_update_chan_resume(data); + } + Ok(()) + }, + Err(()) => { // We weren't able to watch the channel to begin with, so no // updates should be made on it. Previously, full_stack_target // found an (unreachable) panic when the monitor update contained // within `shutdown_finish` was applied. - funded_chan.unset_funding_info(); - ChannelError::close("Channel ID was a duplicate".to_owned()) - }) - .map(|persist_status| (funded_chan, persist_status)) - }) - { - Ok((funded_chan, persist_status)) => { - if let Some(data) = self.handle_initial_monitor( - &mut peer_state.in_flight_monitor_updates, - &mut peer_state.monitor_update_blocked_actions, - &mut peer_state.pending_msg_events, - peer_state.is_connected, - funded_chan, - persist_status, - ) { - mem::drop(peer_state_lock); - mem::drop(per_peer_state); - self.handle_post_monitor_update_chan_resume(data); + chan.unfund(); + try_channel_entry!(self, peer_state, Err(ChannelError::close( + "Channel ID was a duplicate".to_owned() + )), chan_entry) + }, } - Ok(()) }, Err(e) => try_channel_entry!(self, peer_state, Err(e), chan_entry), } @@ -12309,7 +12310,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ hash_map::Entry::Occupied(mut chan_entry) => { let chan = chan_entry.get_mut(); let logger = WithChannelContext::from(&self.logger, &chan.context(), None); - let funding_txo = chan.funding().get_funding_txo(); + let funding_txo = chan.get_funding_txo(); let res = chan.commitment_signed( msg, best_block, @@ -12383,7 +12384,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ hash_map::Entry::Occupied(mut chan_entry) => { let chan = chan_entry.get_mut(); let logger = WithChannelContext::from(&self.logger, &chan.context(), None); - let funding_txo = chan.funding().get_funding_txo(); + let funding_txo = chan.get_funding_txo(); if let Some(chan) = chan.as_funded_mut() { let monitor_update_opt = try_channel_entry!( self, peer_state, chan.commitment_signed_batch(batch, &self.fee_estimator, &&logger), chan_entry diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 2d971c3a100..b29cf2860f7 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -1212,7 +1212,7 @@ macro_rules! get_channel_type_features { peer_state_lock, $channel_id ); - chan.funding().get_channel_type().clone() + chan.get_channel_type().clone() }}; } diff --git a/lightning/src/ln/htlc_reserve_unit_tests.rs b/lightning/src/ln/htlc_reserve_unit_tests.rs index d88b9a2dc3f..01551b0f9c1 100644 --- a/lightning/src/ln/htlc_reserve_unit_tests.rs +++ b/lightning/src/ln/htlc_reserve_unit_tests.rs @@ -85,7 +85,13 @@ fn do_test_counterparty_no_reserve(send_from_initiator: bool) { temp_channel_id ); assert!(channel.is_unfunded_v1()); - channel.funding_mut().holder_selected_channel_reserve_satoshis = 0; + if let Some(chan) = channel.as_unfunded_outbound_v1_mut() { + chan.funding.holder_selected_channel_reserve_satoshis = 0; + } else if let Some(chan) = channel.as_unfunded_inbound_v1_mut() { + chan.funding.holder_selected_channel_reserve_satoshis = 0; + } else { + panic!("expected unfunded v1 channel"); + } channel.context_mut().holder_max_htlc_value_in_flight_msat = 100_000_000; } @@ -905,11 +911,13 @@ pub fn do_test_fee_spike_buffer(cfg: Option, htlc_fails: bool) { let channel = get_channel_ref!(nodes[0], nodes[1], per_peer_lock, peer_state_lock, chan.2); let chan_signer = channel.as_funded().unwrap().get_signer(); + let complete_params = + channel.as_funded().unwrap().funding.channel_transaction_parameters.clone(); let (commitment_tx, _stats) = SpecTxBuilder {}.build_commitment_transaction( false, commitment_number, &remote_point, - &channel.funding().channel_transaction_parameters, + &complete_params, &secp_ctx, local_chan_balance_msat, vec![accepted_htlc_info], @@ -917,11 +925,16 @@ pub fn do_test_fee_spike_buffer(cfg: Option, htlc_fails: bool) { MIN_CHAN_DUST_LIMIT_SATOSHIS, &nodes[0].logger, ); - let params = &channel.funding().channel_transaction_parameters; chan_signer .as_ecdsa() .unwrap() - .sign_counterparty_commitment(params, &commitment_tx, Vec::new(), Vec::new(), &secp_ctx) + .sign_counterparty_commitment( + &complete_params, + &commitment_tx, + Vec::new(), + Vec::new(), + &secp_ctx, + ) .unwrap() }; @@ -2199,7 +2212,7 @@ pub fn do_test_dust_limit_fee_accounting(can_afford: bool) { let chan = get_channel_ref!(nodes[1], nodes[0], per_peer_state_lock, peer_state_lock, chan_id); assert_eq!( - chan.funding().holder_selected_channel_reserve_satoshis, + chan.as_funded().unwrap().funding.holder_selected_channel_reserve_satoshis, channel_reserve_satoshis ); } @@ -2359,6 +2372,8 @@ pub fn do_test_dust_limit_fee_accounting(can_afford: bool) { get_channel_ref!(nodes[0], nodes[1], per_peer_lock, peer_state_lock, chan_id); let chan_signer = channel.as_funded().unwrap().get_signer(); + let complete_params = + channel.as_funded().unwrap().funding.channel_transaction_parameters.clone(); let commitment_tx = CommitmentTransaction::new( commitment_number, &remote_point, @@ -2366,15 +2381,14 @@ pub fn do_test_dust_limit_fee_accounting(can_afford: bool) { local_chan_balance, FEERATE_PER_KW, htlcs, - &channel.funding().channel_transaction_parameters.as_counterparty_broadcastable(), + &complete_params.as_counterparty_broadcastable(), &secp_ctx, ); - let params = &channel.funding().channel_transaction_parameters; chan_signer .as_ecdsa() .unwrap() .sign_counterparty_commitment( - params, + &complete_params, &commitment_tx, Vec::new(), Vec::new(), diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index b5cbe0fee98..974a433137b 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -2035,7 +2035,7 @@ fn test_trivial_inflight_htlc_tracking() { let chan_1_used_liquidity = inflight_htlcs.used_liquidity_msat( &NodeId::from_pubkey(&node_a_id), &NodeId::from_pubkey(&node_b_id), - channel_1.funding().get_short_channel_id().unwrap(), + channel_1.get_short_channel_id().unwrap(), ); assert_eq!(chan_1_used_liquidity, None); } @@ -2048,7 +2048,7 @@ fn test_trivial_inflight_htlc_tracking() { let chan_2_used_liquidity = inflight_htlcs.used_liquidity_msat( &NodeId::from_pubkey(&node_b_id), &NodeId::from_pubkey(&node_c_id), - channel_2.funding().get_short_channel_id().unwrap(), + channel_2.get_short_channel_id().unwrap(), ); assert_eq!(chan_2_used_liquidity, None); @@ -2076,7 +2076,7 @@ fn test_trivial_inflight_htlc_tracking() { let chan_1_used_liquidity = inflight_htlcs.used_liquidity_msat( &NodeId::from_pubkey(&node_a_id), &NodeId::from_pubkey(&node_b_id), - channel_1.funding().get_short_channel_id().unwrap(), + channel_1.get_short_channel_id().unwrap(), ); // First hop accounts for expected 1000 msat fee assert_eq!(chan_1_used_liquidity, Some(501000)); @@ -2090,7 +2090,7 @@ fn test_trivial_inflight_htlc_tracking() { let chan_2_used_liquidity = inflight_htlcs.used_liquidity_msat( &NodeId::from_pubkey(&node_b_id), &NodeId::from_pubkey(&node_c_id), - channel_2.funding().get_short_channel_id().unwrap(), + channel_2.get_short_channel_id().unwrap(), ); assert_eq!(chan_2_used_liquidity, Some(500000)); @@ -2118,7 +2118,7 @@ fn test_trivial_inflight_htlc_tracking() { let chan_1_used_liquidity = inflight_htlcs.used_liquidity_msat( &NodeId::from_pubkey(&node_a_id), &NodeId::from_pubkey(&node_b_id), - channel_1.funding().get_short_channel_id().unwrap(), + channel_1.get_short_channel_id().unwrap(), ); assert_eq!(chan_1_used_liquidity, None); } @@ -2131,7 +2131,7 @@ fn test_trivial_inflight_htlc_tracking() { let chan_2_used_liquidity = inflight_htlcs.used_liquidity_msat( &NodeId::from_pubkey(&node_b_id), &NodeId::from_pubkey(&node_c_id), - channel_2.funding().get_short_channel_id().unwrap(), + channel_2.get_short_channel_id().unwrap(), ); assert_eq!(chan_2_used_liquidity, None); } @@ -2181,7 +2181,7 @@ fn test_holding_cell_inflight_htlcs() { let used_liquidity = inflight_htlcs.used_liquidity_msat( &NodeId::from_pubkey(&node_a_id), &NodeId::from_pubkey(&node_b_id), - channel.funding().get_short_channel_id().unwrap(), + channel.get_short_channel_id().unwrap(), ); assert_eq!(used_liquidity, Some(2000000)); diff --git a/lightning/src/ln/update_fee_tests.rs b/lightning/src/ln/update_fee_tests.rs index ac566393bdb..b7bf9cc03a4 100644 --- a/lightning/src/ln/update_fee_tests.rs +++ b/lightning/src/ln/update_fee_tests.rs @@ -483,6 +483,8 @@ pub fn do_test_update_fee_that_funder_cannot_afford(channel_type_features: Chann let local_chan_signer = local_chan.as_funded().unwrap().get_signer(); let nondust_htlcs: Vec = vec![]; + let complete_params = + local_chan.as_funded().unwrap().funding.channel_transaction_parameters.clone(); let commitment_tx = CommitmentTransaction::new( INITIAL_COMMITMENT_NUMBER - 1, &remote_point, @@ -492,14 +494,19 @@ pub fn do_test_update_fee_that_funder_cannot_afford(channel_type_features: Chann - commit_tx_fee_msat(non_buffer_feerate + 4, 0, &channel_type_features) / 1000, non_buffer_feerate + 4, nondust_htlcs, - &local_chan.funding().channel_transaction_parameters.as_counterparty_broadcastable(), + &complete_params.as_counterparty_broadcastable(), &secp_ctx, ); - let params = &local_chan.funding().channel_transaction_parameters; local_chan_signer .as_ecdsa() .unwrap() - .sign_counterparty_commitment(params, &commitment_tx, Vec::new(), Vec::new(), &secp_ctx) + .sign_counterparty_commitment( + &complete_params, + &commitment_tx, + Vec::new(), + Vec::new(), + &secp_ctx, + ) .unwrap() }; @@ -583,6 +590,8 @@ pub fn test_update_fee_that_saturates_subs() { get_channel_ref!(nodes[0], nodes[1], per_peer_lock, peer_state_lock, chan_id); let local_chan_signer = local_chan.as_funded().unwrap().get_signer(); let nondust_htlcs: Vec = vec![]; + let complete_params = + local_chan.as_funded().unwrap().funding.channel_transaction_parameters.clone(); let commitment_tx = CommitmentTransaction::new( INITIAL_COMMITMENT_NUMBER, &remote_point, @@ -592,14 +601,19 @@ pub fn test_update_fee_that_saturates_subs() { 0, FEERATE, nondust_htlcs, - &local_chan.funding().channel_transaction_parameters.as_counterparty_broadcastable(), + &complete_params.as_counterparty_broadcastable(), &secp_ctx, ); - let params = &local_chan.funding().channel_transaction_parameters; local_chan_signer .as_ecdsa() .unwrap() - .sign_counterparty_commitment(params, &commitment_tx, Vec::new(), Vec::new(), &secp_ctx) + .sign_counterparty_commitment( + &complete_params, + &commitment_tx, + Vec::new(), + Vec::new(), + &secp_ctx, + ) .unwrap() }; @@ -1083,7 +1097,7 @@ pub fn do_cannot_afford_on_holding_cell_release( let chan = get_channel_ref!(nodes[1], nodes[0], per_peer_state_lock, peer_state_lock, chan_id); assert_eq!( - chan.funding().holder_selected_channel_reserve_satoshis, + chan.as_funded().unwrap().funding.holder_selected_channel_reserve_satoshis, channel_reserve_satoshis ); } @@ -1270,7 +1284,7 @@ pub fn do_can_afford_given_trimmed_htlcs(inequality_regions: core::cmp::Ordering let chan = get_channel_ref!(nodes[1], nodes[0], per_peer_state_lock, peer_state_lock, chan_id); assert_eq!( - chan.funding().holder_selected_channel_reserve_satoshis, + chan.as_funded().unwrap().funding.holder_selected_channel_reserve_satoshis, channel_reserve_satoshis ); } diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 84bfbb902ea..b59dc27b017 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -1617,24 +1617,18 @@ impl ChannelSigner for InMemorySigner { } } -const MISSING_PARAMS_ERR: &'static str = - "ChannelTransactionParameters must be populated before signing operations"; - impl EcdsaChannelSigner for InMemorySigner { fn sign_counterparty_commitment( &self, channel_parameters: &ChannelTransactionParameters, commitment_tx: &CommitmentTransaction, _inbound_htlc_preimages: Vec, _outbound_htlc_preimages: Vec, secp_ctx: &Secp256k1, ) -> Result<(Signature, Vec), ()> { - assert!(channel_parameters.is_populated(), "Channel parameters must be fully populated"); - let trusted_tx = commitment_tx.trust(); let keys = trusted_tx.keys(); let funding_key = self.funding_key(channel_parameters.splice_parent_funding_txid); let funding_pubkey = funding_key.public_key(secp_ctx); - let counterparty_keys = - channel_parameters.counterparty_pubkeys().expect(MISSING_PARAMS_ERR); + let counterparty_keys = &channel_parameters.counterparty_parameters.pubkeys; let channel_funding_redeemscript = make_funding_redeemscript(&funding_pubkey, &counterparty_keys.funding_pubkey); @@ -1693,12 +1687,9 @@ impl EcdsaChannelSigner for InMemorySigner { &self, channel_parameters: &ChannelTransactionParameters, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1, ) -> Result { - assert!(channel_parameters.is_populated(), "Channel parameters must be fully populated"); - let funding_key = self.funding_key(channel_parameters.splice_parent_funding_txid); let funding_pubkey = funding_key.public_key(secp_ctx); - let counterparty_keys = - channel_parameters.counterparty_pubkeys().expect(MISSING_PARAMS_ERR); + let counterparty_keys = &channel_parameters.counterparty_parameters.pubkeys; let funding_redeemscript = make_funding_redeemscript(&funding_pubkey, &counterparty_keys.funding_pubkey); let trusted_tx = commitment_tx.trust(); @@ -1716,12 +1707,9 @@ impl EcdsaChannelSigner for InMemorySigner { &self, channel_parameters: &ChannelTransactionParameters, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1, ) -> Result { - assert!(channel_parameters.is_populated(), "Channel parameters must be fully populated"); - let funding_key = self.funding_key(channel_parameters.splice_parent_funding_txid); let funding_pubkey = funding_key.public_key(secp_ctx); - let counterparty_keys = - channel_parameters.counterparty_pubkeys().expect(MISSING_PARAMS_ERR); + let counterparty_keys = &channel_parameters.counterparty_parameters.pubkeys; let funding_redeemscript = make_funding_redeemscript(&funding_pubkey, &counterparty_keys.funding_pubkey); let trusted_tx = commitment_tx.trust(); @@ -1739,8 +1727,6 @@ impl EcdsaChannelSigner for InMemorySigner { input: usize, amount: u64, per_commitment_key: &SecretKey, secp_ctx: &Secp256k1, ) -> Result { - assert!(channel_parameters.is_populated(), "Channel parameters must be fully populated"); - let revocation_key = chan_utils::derive_private_revocation_key( &secp_ctx, &per_commitment_key, @@ -1753,8 +1739,7 @@ impl EcdsaChannelSigner for InMemorySigner { &per_commitment_point, ); let witness_script = { - let counterparty_keys = - channel_parameters.counterparty_pubkeys().expect(MISSING_PARAMS_ERR); + let counterparty_keys = &channel_parameters.counterparty_parameters.pubkeys; let holder_selected_contest_delay = channel_parameters.holder_selected_contest_delay; let counterparty_delayedpubkey = DelayedPaymentKey::from_basepoint( &secp_ctx, @@ -1786,8 +1771,6 @@ impl EcdsaChannelSigner for InMemorySigner { input: usize, amount: u64, per_commitment_key: &SecretKey, htlc: &HTLCOutputInCommitment, secp_ctx: &Secp256k1, ) -> Result { - assert!(channel_parameters.is_populated(), "Channel parameters must be fully populated"); - let revocation_key = chan_utils::derive_private_revocation_key( &secp_ctx, &per_commitment_key, @@ -1800,8 +1783,7 @@ impl EcdsaChannelSigner for InMemorySigner { &per_commitment_point, ); let witness_script = { - let counterparty_keys = - channel_parameters.counterparty_pubkeys().expect(MISSING_PARAMS_ERR); + let counterparty_keys = &channel_parameters.counterparty_parameters.pubkeys; let counterparty_htlcpubkey = HtlcKey::from_basepoint( &secp_ctx, &counterparty_keys.htlc_basepoint, @@ -1838,10 +1820,6 @@ impl EcdsaChannelSigner for InMemorySigner { &self, htlc_tx: &Transaction, input: usize, htlc_descriptor: &HTLCDescriptor, secp_ctx: &Secp256k1, ) -> Result { - let channel_parameters = - &htlc_descriptor.channel_derivation_parameters.transaction_parameters; - assert!(channel_parameters.is_populated(), "Channel parameters must be fully populated"); - let witness_script = htlc_descriptor.witness_script(secp_ctx); let sighash = &sighash::SighashCache::new(&*htlc_tx) .p2wsh_signature_hash( @@ -1865,8 +1843,6 @@ impl EcdsaChannelSigner for InMemorySigner { input: usize, amount: u64, per_commitment_point: &PublicKey, htlc: &HTLCOutputInCommitment, secp_ctx: &Secp256k1, ) -> Result { - assert!(channel_parameters.is_populated(), "Channel parameters must be fully populated"); - let htlc_key = chan_utils::derive_private_key(&secp_ctx, &per_commitment_point, &self.htlc_base_key); let revocation_pubkey = RevocationKey::from_basepoint( @@ -1874,8 +1850,7 @@ impl EcdsaChannelSigner for InMemorySigner { &channel_parameters.holder_pubkeys.revocation_basepoint, &per_commitment_point, ); - let counterparty_keys = - channel_parameters.counterparty_pubkeys().expect(MISSING_PARAMS_ERR); + let counterparty_keys = &channel_parameters.counterparty_parameters.pubkeys; let counterparty_htlcpubkey = HtlcKey::from_basepoint( &secp_ctx, &counterparty_keys.htlc_basepoint, @@ -1909,12 +1884,10 @@ impl EcdsaChannelSigner for InMemorySigner { &self, channel_parameters: &ChannelTransactionParameters, closing_tx: &ClosingTransaction, secp_ctx: &Secp256k1, ) -> Result { - assert!(channel_parameters.is_populated(), "Channel parameters must be fully populated"); - let funding_key = self.funding_key(channel_parameters.splice_parent_funding_txid); let funding_pubkey = funding_key.public_key(secp_ctx); let counterparty_funding_key = - &channel_parameters.counterparty_pubkeys().expect(MISSING_PARAMS_ERR).funding_pubkey; + &channel_parameters.counterparty_parameters.pubkeys.funding_pubkey; let channel_funding_redeemscript = make_funding_redeemscript(&funding_pubkey, counterparty_funding_key); Ok(closing_tx.trust().sign( @@ -1929,8 +1902,6 @@ impl EcdsaChannelSigner for InMemorySigner { &self, chan_params: &ChannelTransactionParameters, anchor_tx: &Transaction, input: usize, secp_ctx: &Secp256k1, ) -> Result { - assert!(chan_params.is_populated(), "Channel parameters must be fully populated"); - let witness_script = chan_utils::get_keyed_anchor_redeemscript(&chan_params.holder_pubkeys.funding_pubkey); let amt = Amount::from_sat(ANCHOR_OUTPUT_VALUE_SATOSHI); @@ -1954,20 +1925,15 @@ impl EcdsaChannelSigner for InMemorySigner { &self, channel_parameters: &ChannelTransactionParameters, tx: &Transaction, input_index: usize, secp_ctx: &Secp256k1, ) -> Signature { - assert!(channel_parameters.is_populated(), "Channel parameters must be fully populated"); assert_eq!( tx.input[input_index].previous_output, - channel_parameters - .funding_outpoint - .as_ref() - .expect("Funding outpoint must be known prior to signing") - .into_bitcoin_outpoint() + channel_parameters.funding_outpoint.into_bitcoin_outpoint() ); let funding_key = self.funding_key(channel_parameters.splice_parent_funding_txid); let funding_pubkey = funding_key.public_key(secp_ctx); let counterparty_funding_key = - &channel_parameters.counterparty_pubkeys().expect(MISSING_PARAMS_ERR).funding_pubkey; + &channel_parameters.counterparty_parameters.pubkeys.funding_pubkey; let funding_redeemscript = make_funding_redeemscript(&funding_pubkey, counterparty_funding_key); let sighash = &sighash::SighashCache::new(tx) diff --git a/lightning/src/util/test_channel_signer.rs b/lightning/src/util/test_channel_signer.rs index 70eb3223bc4..8b1a3b9bcb4 100644 --- a/lightning/src/util/test_channel_signer.rs +++ b/lightning/src/util/test_channel_signer.rs @@ -429,7 +429,7 @@ impl EcdsaChannelSigner for TestChannelSigner { .unwrap(); let countersignatory_htlc_key = HtlcKey::from_basepoint( &secp_ctx, - &channel_parameters.counterparty_pubkeys().unwrap().htlc_basepoint, + &channel_parameters.counterparty_parameters.pubkeys.htlc_basepoint, &htlc_descriptor.per_commitment_point, ); @@ -482,7 +482,7 @@ impl EcdsaChannelSigner for TestChannelSigner { return Err(()); } closing_tx - .verify(channel_parameters.funding_outpoint.as_ref().unwrap().into_bitcoin_outpoint()) + .verify(channel_parameters.funding_outpoint.into_bitcoin_outpoint()) .expect("derived different closing transaction"); Ok(self.inner.sign_closing_transaction(channel_parameters, closing_tx, secp_ctx).unwrap()) }