+// 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,
+ }
+ }
+}
+