+// A not-yet-funded outbound (from holder) channel using V2 channel establishment.
+#[cfg(any(dual_funding, splicing))]
+pub(super) struct OutboundV2Channel<SP: Deref> where SP::Target: SignerProvider {
+ pub context: ChannelContext<SP>,
+ pub unfunded_context: UnfundedChannelContext,
+ #[cfg(any(dual_funding, splicing))]
+ pub dual_funding_context: DualFundingChannelContext,
+}
+
+#[cfg(any(dual_funding, splicing))]
+impl<SP: Deref> OutboundV2Channel<SP> where SP::Target: SignerProvider {
+ pub fn new<ES: Deref, F: Deref>(
+ fee_estimator: &LowerBoundedFeeEstimator<F>, entropy_source: &ES, signer_provider: &SP,
+ counterparty_node_id: PublicKey, their_features: &InitFeatures, funding_satoshis: u64,
+ user_id: u128, config: &UserConfig, current_chain_height: u32, outbound_scid_alias: u64,
+ funding_confirmation_target: ConfirmationTarget,
+ ) -> Result<OutboundV2Channel<SP>, APIError>
+ where ES::Target: EntropySource,
+ F::Target: FeeEstimator,
+ {
+ let channel_keys_id = signer_provider.generate_channel_keys_id(false, funding_satoshis, user_id);
+ let holder_signer = signer_provider.derive_channel_signer(funding_satoshis, channel_keys_id);
+ let pubkeys = holder_signer.pubkeys().clone();
+
+ let temporary_channel_id = Some(ChannelId::temporary_v2_from_revocation_basepoint(&pubkeys.revocation_basepoint));
+
+ let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
+ funding_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS);
+
+ let funding_feerate_sat_per_1000_weight = fee_estimator.bounded_sat_per_1000_weight(funding_confirmation_target);
+ let funding_tx_locktime = current_chain_height;
+
+ let chan = Self {
+ context: ChannelContext::new_for_outbound_channel(
+ fee_estimator,
+ entropy_source,
+ signer_provider,
+ counterparty_node_id,
+ their_features,
+ funding_satoshis,
+ 0,
+ user_id,
+ config,
+ current_chain_height,
+ outbound_scid_alias,
+ temporary_channel_id,
+ holder_selected_channel_reserve_satoshis,
+ channel_keys_id,
+ holder_signer,
+ pubkeys,
+ )?,
+ unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 },
+ dual_funding_context: DualFundingChannelContext {
+ our_funding_satoshis: funding_satoshis,
+ their_funding_satoshis: 0,
+ funding_tx_locktime,
+ funding_feerate_sat_per_1000_weight,
+ }
+ };
+ Ok(chan)
+ }
+
+ /// If we receive an error message, it may only be a rejection of the channel type we tried,
+ /// not of our ability to open any channel at all. Thus, on error, we should first call this
+ /// and see if we get a new `OpenChannelV2` message, otherwise the channel is failed.
+ pub(crate) fn maybe_handle_error_without_close<F: Deref>(
+ &mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>
+ ) -> Result<msgs::OpenChannelV2, ()>
+ where
+ F::Target: FeeEstimator
+ {
+ self.context.maybe_downgrade_channel_features(fee_estimator)?;
+ Ok(self.get_open_channel_v2(chain_hash))
+ }
+
+ pub fn get_open_channel_v2(&self, chain_hash: ChainHash) -> msgs::OpenChannelV2 {
+ if self.context.have_received_message() {
+ debug_assert!(false, "Cannot generate an open_channel2 after we've moved forward");
+ }
+
+ if self.context.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER {
+ debug_assert!(false, "Tried to send an open_channel2 for a channel that has already advanced");
+ }
+
+ let first_per_commitment_point = self.context.holder_signer.as_ref()
+ .get_per_commitment_point(self.context.cur_holder_commitment_transaction_number,
+ &self.context.secp_ctx);
+ let second_per_commitment_point = self.context.holder_signer.as_ref()
+ .get_per_commitment_point(self.context.cur_holder_commitment_transaction_number - 1,
+ &self.context.secp_ctx);
+ let keys = self.context.get_holder_pubkeys();
+
+ msgs::OpenChannelV2 {
+ common_fields: msgs::CommonOpenChannelFields {
+ chain_hash,
+ temporary_channel_id: self.context.temporary_channel_id.unwrap(),
+ funding_satoshis: self.context.channel_value_satoshis,
+ dust_limit_satoshis: self.context.holder_dust_limit_satoshis,
+ max_htlc_value_in_flight_msat: self.context.holder_max_htlc_value_in_flight_msat,
+ htlc_minimum_msat: self.context.holder_htlc_minimum_msat,
+ commitment_feerate_sat_per_1000_weight: self.context.feerate_per_kw,
+ to_self_delay: self.context.get_holder_selected_contest_delay(),
+ max_accepted_htlcs: self.context.holder_max_accepted_htlcs,
+ funding_pubkey: keys.funding_pubkey,
+ revocation_basepoint: keys.revocation_basepoint.to_public_key(),
+ payment_basepoint: keys.payment_point,
+ delayed_payment_basepoint: keys.delayed_payment_basepoint.to_public_key(),
+ htlc_basepoint: keys.htlc_basepoint.to_public_key(),
+ first_per_commitment_point,
+ channel_flags: if self.context.config.announced_channel {1} else {0},
+ shutdown_scriptpubkey: Some(match &self.context.shutdown_scriptpubkey {
+ Some(script) => script.clone().into_inner(),
+ None => Builder::new().into_script(),
+ }),
+ channel_type: Some(self.context.channel_type.clone()),
+ },
+ funding_feerate_sat_per_1000_weight: self.context.feerate_per_kw,
+ second_per_commitment_point,
+ locktime: self.dual_funding_context.funding_tx_locktime,
+ require_confirmed_inputs: None,
+ }
+ }
+}
+
+// A not-yet-funded inbound (from counterparty) channel using V2 channel establishment.
+#[cfg(any(dual_funding, splicing))]
+pub(super) struct InboundV2Channel<SP: Deref> where SP::Target: SignerProvider {
+ pub context: ChannelContext<SP>,
+ pub unfunded_context: UnfundedChannelContext,
+ pub dual_funding_context: DualFundingChannelContext,
+}
+
+#[cfg(any(dual_funding, splicing))]
+impl<SP: Deref> InboundV2Channel<SP> where SP::Target: SignerProvider {
+ /// Creates a new dual-funded channel from a remote side's request for one.
+ /// Assumes chain_hash has already been checked and corresponds with what we expect!
+ pub fn new<ES: Deref, F: Deref, L: Deref>(
+ fee_estimator: &LowerBoundedFeeEstimator<F>, entropy_source: &ES, signer_provider: &SP,
+ counterparty_node_id: PublicKey, our_supported_features: &ChannelTypeFeatures,
+ their_features: &InitFeatures, msg: &msgs::OpenChannelV2, funding_satoshis: u64, user_id: u128,
+ config: &UserConfig, current_chain_height: u32, logger: &L,
+ ) -> Result<InboundV2Channel<SP>, ChannelError>
+ where ES::Target: EntropySource,
+ F::Target: FeeEstimator,
+ L::Target: Logger,
+ {
+ let channel_value_satoshis = funding_satoshis.saturating_add(msg.common_fields.funding_satoshis);
+ let counterparty_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
+ channel_value_satoshis, msg.common_fields.dust_limit_satoshis);
+ let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
+ channel_value_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS);
+
+ // First check the channel type is known, failing before we do anything else if we don't
+ // support this channel type.
+ if msg.common_fields.channel_type.is_none() {
+ return Err(ChannelError::Close(format!("Rejecting V2 channel {} missing channel_type",
+ msg.common_fields.temporary_channel_id)))
+ }
+ let channel_type = channel_type_from_open_channel(&msg.common_fields, their_features, our_supported_features)?;
+
+ let counterparty_pubkeys = ChannelPublicKeys {
+ funding_pubkey: msg.common_fields.funding_pubkey,
+ revocation_basepoint: RevocationBasepoint(msg.common_fields.revocation_basepoint),
+ payment_point: msg.common_fields.payment_basepoint,
+ delayed_payment_basepoint: DelayedPaymentBasepoint(msg.common_fields.delayed_payment_basepoint),
+ htlc_basepoint: HtlcBasepoint(msg.common_fields.htlc_basepoint)
+ };
+
+ let mut context = ChannelContext::new_for_inbound_channel(
+ fee_estimator,
+ entropy_source,
+ signer_provider,
+ counterparty_node_id,
+ their_features,
+ user_id,
+ config,
+ current_chain_height,
+ logger,
+ false,
+
+ funding_satoshis,
+
+ counterparty_pubkeys,
+ channel_type,
+ holder_selected_channel_reserve_satoshis,
+ counterparty_selected_channel_reserve_satoshis,
+ 0 /* push_msat not used in dual-funding */,
+ msg.common_fields.clone(),
+ )?;
+ let channel_id = ChannelId::v2_from_revocation_basepoints(
+ &context.get_holder_pubkeys().revocation_basepoint,
+ &context.get_counterparty_pubkeys().revocation_basepoint);
+ context.channel_id = channel_id;
+
+ let chan = Self {
+ context,
+ unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 },
+ dual_funding_context: DualFundingChannelContext {
+ our_funding_satoshis: funding_satoshis,
+ their_funding_satoshis: msg.common_fields.funding_satoshis,
+ funding_tx_locktime: msg.locktime,
+ funding_feerate_sat_per_1000_weight: msg.funding_feerate_sat_per_1000_weight,
+ }
+ };
+
+ Ok(chan)
+ }
+
+ /// Marks an inbound channel as accepted and generates a [`msgs::AcceptChannelV2`] message which
+ /// should be sent back to the counterparty node.
+ ///
+ /// [`msgs::AcceptChannelV2`]: crate::ln::msgs::AcceptChannelV2
+ pub fn accept_inbound_dual_funded_channel(&mut self) -> msgs::AcceptChannelV2 {
+ if self.context.is_outbound() {
+ debug_assert!(false, "Tried to send accept_channel for an outbound channel?");
+ }
+ if !matches!(
+ self.context.channel_state, ChannelState::NegotiatingFunding(flags)
+ if flags == (NegotiatingFundingFlags::OUR_INIT_SENT | NegotiatingFundingFlags::THEIR_INIT_SENT)
+ ) {
+ debug_assert!(false, "Tried to send accept_channel2 after channel had moved forward");
+ }
+ if self.context.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER {
+ debug_assert!(false, "Tried to send an accept_channel2 for a channel that has already advanced");
+ }
+
+ self.generate_accept_channel_v2_message()
+ }
+
+ /// This function is used to explicitly generate a [`msgs::AcceptChannel`] message for an
+ /// inbound channel. If the intention is to accept an inbound channel, use
+ /// [`InboundV1Channel::accept_inbound_channel`] instead.
+ ///
+ /// [`msgs::AcceptChannelV2`]: crate::ln::msgs::AcceptChannelV2
+ fn generate_accept_channel_v2_message(&self) -> msgs::AcceptChannelV2 {
+ let first_per_commitment_point = self.context.holder_signer.as_ref().get_per_commitment_point(
+ self.context.cur_holder_commitment_transaction_number, &self.context.secp_ctx);
+ let second_per_commitment_point = self.context.holder_signer.as_ref().get_per_commitment_point(
+ self.context.cur_holder_commitment_transaction_number - 1, &self.context.secp_ctx);
+ let keys = self.context.get_holder_pubkeys();
+
+ msgs::AcceptChannelV2 {
+ common_fields: msgs::CommonAcceptChannelFields {
+ temporary_channel_id: self.context.temporary_channel_id.unwrap(),
+ dust_limit_satoshis: self.context.holder_dust_limit_satoshis,
+ max_htlc_value_in_flight_msat: self.context.holder_max_htlc_value_in_flight_msat,
+ htlc_minimum_msat: self.context.holder_htlc_minimum_msat,
+ minimum_depth: self.context.minimum_depth.unwrap(),
+ to_self_delay: self.context.get_holder_selected_contest_delay(),
+ max_accepted_htlcs: self.context.holder_max_accepted_htlcs,
+ funding_pubkey: keys.funding_pubkey,
+ revocation_basepoint: keys.revocation_basepoint.to_public_key(),
+ payment_basepoint: keys.payment_point,
+ delayed_payment_basepoint: keys.delayed_payment_basepoint.to_public_key(),
+ htlc_basepoint: keys.htlc_basepoint.to_public_key(),
+ first_per_commitment_point,
+ shutdown_scriptpubkey: Some(match &self.context.shutdown_scriptpubkey {
+ Some(script) => script.clone().into_inner(),
+ None => Builder::new().into_script(),
+ }),
+ channel_type: Some(self.context.channel_type.clone()),
+ },
+ funding_satoshis: self.dual_funding_context.our_funding_satoshis,
+ second_per_commitment_point,
+ require_confirmed_inputs: None,
+ }
+ }
+
+ /// Enables the possibility for tests to extract a [`msgs::AcceptChannelV2`] message for an
+ /// inbound channel without accepting it.
+ ///
+ /// [`msgs::AcceptChannelV2`]: crate::ln::msgs::AcceptChannelV2
+ #[cfg(test)]
+ pub fn get_accept_channel_v2_message(&self) -> msgs::AcceptChannelV2 {
+ self.generate_accept_channel_v2_message()
+ }
+}
+
+// Unfunded channel utilities
+
+fn get_initial_channel_type(config: &UserConfig, their_features: &InitFeatures) -> ChannelTypeFeatures {
+ // The default channel type (ie the first one we try) depends on whether the channel is
+ // public - if it is, we just go with `only_static_remotekey` as it's the only option
+ // available. If it's private, we first try `scid_privacy` as it provides better privacy
+ // with no other changes, and fall back to `only_static_remotekey`.
+ let mut ret = ChannelTypeFeatures::only_static_remote_key();
+ if !config.channel_handshake_config.announced_channel &&
+ config.channel_handshake_config.negotiate_scid_privacy &&
+ their_features.supports_scid_privacy() {
+ ret.set_scid_privacy_required();
+ }
+
+ // Optionally, if the user would like to negotiate the `anchors_zero_fee_htlc_tx` option, we
+ // set it now. If they don't understand it, we'll fall back to our default of
+ // `only_static_remotekey`.
+ if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx &&
+ their_features.supports_anchors_zero_fee_htlc_tx() {
+ ret.set_anchors_zero_fee_htlc_tx_required();
+ }
+
+ ret
+}
+
+const SERIALIZATION_VERSION: u8 = 4;