From 7c5321f4e2432572da800326c2466d87e3c48f4b Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Mon, 22 May 2023 16:46:01 +0200 Subject: [PATCH] wip: add V2 channels --- lightning/src/ln/channel.rs | 521 +++++++++++++++++++++++++++++++++--- 1 file changed, 483 insertions(+), 38 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index f594239316b..154f21a2436 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -252,9 +252,11 @@ enum HTLCUpdateAwaitingACK { /// ChannelReady can then get all remaining flags set on it, until we finish shutdown, then we /// move on to ShutdownComplete, at which point most calls into this channel are disallowed. enum ChannelState { - /// Implies we have (or are prepared to) send our open_channel/accept_channel message + /// Implies we have (or are prepared to) send our open_channel/accept_channel message or in the + /// case of V2 establishment, our open_channel2/accept_channel2 message OurInitSent = 1 << 0, - /// Implies we have received their open_channel/accept_channel message + /// Implies we have received their open_channel/accept_channel message or in the case of + /// V2 establishment, their open_channel2/accept_channel2 message TheirInitSent = 1 << 1, /// We have sent funding_created and are awaiting a funding_signed to advance to FundingSent. /// Note that this is nonsense for an inbound channel as we immediately generate funding_signed @@ -1890,6 +1892,20 @@ pub(super) trait ChannelInterface<'a, Signer: WriteableEcdsaChannelSigner + 'a> cmp::min(channel_value_satoshis, cmp::max(calculated_reserve, MIN_THEIR_CHAN_RESERVE_SATOSHIS)) } + /// Returns a minimum channel reserve value each party needs to maintain, fixed in the spec to a + /// default of 1% of the total channel value. + /// + /// Guaranteed to return a value no larger than channel_value_satoshis + /// + /// This is used both for outbound and inbound channels and has lower bound + /// of `dust_limit_satoshis`. + fn get_v2_channel_reserve_satoshis(channel_value_satoshis: u64, dust_limit_satoshis: u64) -> u64 { + let channel_reserve_proportional_millionths = 10_000; // Fixed at 1% in spec. + let calculated_reserve = + channel_value_satoshis.saturating_mul(channel_reserve_proportional_millionths) / 1_000_000; + cmp::min(channel_value_satoshis, cmp::max(calculated_reserve, dust_limit_satoshis)) + } + /// This is for legacy reasons, present for forward-compatibility. /// LDK versions older than 0.0.104 don't know how read/handle values other than default /// from storage. Hence, we use this function to not persist default values of @@ -1946,11 +1962,16 @@ pub(super) struct DualFundingChannelContext { /// The common channel context, not specific to a particular channel establishment version. common: ChannelContext, + /// The amount in satoshis we will be contributing to the channel. + our_funding_satoshis: u64, /// The UTXOs we will be adding in interactive transaction construction. our_funding_inputs: Vec, - /// Flag indicating that the channel initiator requires all parties to use confirmed inputs for - /// construction of the funding transaction. - require_confirmed_inputs: bool, + /// Flag indicating that we require our counterparty to use confirmed inputs for the construction + /// of the funding transaction. + we_require_confirmed_inputs: bool, + /// Flag indicating that our counterparty requires us to use confirmed inputs for the construction + /// of the funding transaction. + counterparty_requires_confirmed_inputs: bool, /// The funding transaction locktime suggested by the initiator. If set by us, it is always set /// to the current block height to align incentives against fee-sniping. funding_tx_locktime: u32, @@ -6475,46 +6496,39 @@ pub(super) struct OutboundV2Channel { impl OutboundV2Channel { pub fn new( fee_estimator: &LowerBoundedFeeEstimator, entropy_source: &ES, signer_provider: &SP, - counterparty_node_id: PublicKey, their_features: &InitFeatures, channel_value_satoshis: u64, + counterparty_node_id: PublicKey, their_features: &InitFeatures, funding_satoshis: u64, funding_inputs: Vec, change_script_pubkey: Script, user_id: u128, config: &UserConfig, current_chain_height: u32, outbound_scid_alias: u64, - require_confirmed_inputs: bool, + require_confirmed_inputs: bool, funding_conf_target: Option, ) -> Result, APIError> where ES::Target: EntropySource, SP::Target: SignerProvider, F::Target: FeeEstimator, { let holder_selected_contest_delay = config.channel_handshake_config.our_to_self_delay; - let channel_keys_id = signer_provider.generate_channel_keys_id(false, channel_value_satoshis, user_id); - let holder_signer = signer_provider.derive_channel_signer(channel_value_satoshis, channel_keys_id); + 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(); - if !their_features.supports_wumbo() && channel_value_satoshis > MAX_FUNDING_SATOSHIS_NO_WUMBO { - return Err(APIError::APIMisuseError{err: format!("funding_value must not exceed {}, it was {}", MAX_FUNDING_SATOSHIS_NO_WUMBO, channel_value_satoshis)}); + if !their_features.supports_wumbo() && funding_satoshis > MAX_FUNDING_SATOSHIS_NO_WUMBO { + return Err(APIError::APIMisuseError{err: format!("funding_value must not exceed {}, it was {}", MAX_FUNDING_SATOSHIS_NO_WUMBO, funding_satoshis)}); } - if channel_value_satoshis >= TOTAL_BITCOIN_SUPPLY_SATOSHIS { - return Err(APIError::APIMisuseError{err: format!("funding_value must be smaller than the total bitcoin supply, it was {}", channel_value_satoshis)}); + if funding_satoshis >= TOTAL_BITCOIN_SUPPLY_SATOSHIS { + return Err(APIError::APIMisuseError{err: format!("funding_value must be smaller than the total bitcoin supply, it was {}", funding_satoshis)}); } if holder_selected_contest_delay < BREAKDOWN_TIMEOUT { return Err(APIError::APIMisuseError {err: format!("Configured with an unreasonable our_to_self_delay ({}) putting user funds at risks", holder_selected_contest_delay)}); } - let holder_selected_channel_reserve_satoshis = Channel::::get_holder_selected_channel_reserve_satoshis(channel_value_satoshis, config); - if holder_selected_channel_reserve_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { - // Protocol level safety check in place, although it should never happen because - // of `MIN_THEIR_CHAN_RESERVE_SATOSHIS` - return Err(APIError::APIMisuseError { err: format!("Holder selected channel reserve below implemention limit dust_limit_satoshis {}", holder_selected_channel_reserve_satoshis) }); - } let channel_type = get_initial_channel_type(&config, their_features); debug_assert!(channel_type.is_subset(&channelmanager::provided_channel_type_features(&config))); - // TODO(dual_funding): The spec makes a distinction between a funding feerate and a commitment - // fee rate for opening a V2 channel. We should consider how this might benefit us and how to - // appropriately set each. Here we just use the same feerate. - let feerate = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Normal); + let commitment_tx_feerate = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Background); + let funding_tx_feerate = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Normal); - let value_to_self_msat = channel_value_satoshis * 1000; - let commitment_tx_fee = Self::commit_tx_fee_msat(feerate, MIN_AFFORDABLE_HTLC_COUNT, channel_type.requires_anchors_zero_fee_htlc_tx()); + let value_to_self_msat = funding_satoshis * 1000; + let commitment_tx_fee = Self::commit_tx_fee_msat(commitment_tx_feerate, + MIN_AFFORDABLE_HTLC_COUNT, channel_type.requires_anchors_zero_fee_htlc_tx()); if value_to_self_msat < commitment_tx_fee { return Err(APIError::APIMisuseError{ err: format!("Funding amount ({}) can't even pay fee for initial commitment transaction fee of {}.", value_to_self_msat / 1000, commitment_tx_fee / 1000) }); } @@ -6540,15 +6554,17 @@ impl OutboundV2Channel { Err(_) => return Err(APIError::ChannelUnavailable { err: "Failed to get destination script".to_owned()}), }; - let temporary_channel_id = generate_v2_temporary_channel_id(&pubkeys.revocation_basepoint, + let temporary_channel_id = generate_v2_channel_id(&pubkeys.revocation_basepoint, &PublicKey::from_slice(&[0; 33]).expect("zero point is on secp256k1")); let funding_tx_locktime = current_chain_height; Ok(Self { context: DualFundingChannelContext { + our_funding_satoshis: funding_satoshis, our_funding_inputs: funding_inputs, - require_confirmed_inputs, + we_require_confirmed_inputs: require_confirmed_inputs, + counterparty_requires_confirmed_inputs: false, funding_tx_locktime, our_change_script_pubkey: change_script_pubkey, @@ -6570,7 +6586,8 @@ impl OutboundV2Channel { channel_state: ChannelState::OurInitSent as u32, announcement_sigs_state: AnnouncementSigsState::NotSent, secp_ctx, - channel_value_satoshis, + // We'll add our counterparty's `funding_satoshis` when we receive `accept_channel2`. + channel_value_satoshis: funding_satoshis, latest_monitor_update_id: 0, @@ -6600,10 +6617,12 @@ impl OutboundV2Channel { monitor_pending_failures: Vec::new(), monitor_pending_finalized_fulfills: Vec::new(), + // We'll add our counterparty's `funding_satoshis` to these max commitment output assertions + // when we receive `accept_channel2`. #[cfg(debug_assertions)] - holder_max_commitment_tx_output: Mutex::new((channel_value_satoshis * 1000, 0)), + holder_max_commitment_tx_output: Mutex::new((funding_satoshis * 1000, 0)), #[cfg(debug_assertions)] - counterparty_max_commitment_tx_output: Mutex::new((channel_value_satoshis * 1000, 0)), + counterparty_max_commitment_tx_output: Mutex::new((funding_satoshis * 1000, 0)), last_sent_closing_fee: None, pending_counterparty_closing_signed: None, @@ -6617,13 +6636,19 @@ impl OutboundV2Channel { short_channel_id: None, channel_creation_height: current_chain_height, - feerate_per_kw: feerate, + feerate_per_kw: commitment_tx_feerate, counterparty_dust_limit_satoshis: 0, holder_dust_limit_satoshis: MIN_CHAN_DUST_LIMIT_SATOSHIS, counterparty_max_htlc_value_in_flight_msat: 0, - holder_max_htlc_value_in_flight_msat: Self::get_holder_max_htlc_value_in_flight_msat(channel_value_satoshis, &config.channel_handshake_config), - counterparty_selected_channel_reserve_satoshis: None, // Filled in in accept_channel - holder_selected_channel_reserve_satoshis, + // We'll adjust this to include our counterparty's `funding_satoshis` when we + // receive `accept_channel2`. + holder_max_htlc_value_in_flight_msat: Self::get_holder_max_htlc_value_in_flight_msat( + funding_satoshis, &config.channel_handshake_config), + // We'll adjust the reserves to include our counterparty's `funding_satoshis` when we + // receive `accept_channel2`. + counterparty_selected_channel_reserve_satoshis: None, // Fill + holder_selected_channel_reserve_satoshis: Self::get_v2_channel_reserve_satoshis( + funding_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS), counterparty_htlc_minimum_msat: 0, holder_htlc_minimum_msat: if config.channel_handshake_config.our_htlc_minimum_msat == 0 { 1 } else { config.channel_handshake_config.our_htlc_minimum_msat }, counterparty_max_accepted_htlcs: 0, @@ -6724,7 +6749,7 @@ impl OutboundV2Channel { }), channel_type: Some(self.context.common.channel_type.clone()), locktime: self.context.funding_tx_locktime, - require_confirmed_inputs: if self.context.require_confirmed_inputs { Some(()) } else { None }, + require_confirmed_inputs: if self.context.we_require_confirmed_inputs { Some(()) } else { None }, } } } @@ -6737,7 +6762,424 @@ pub(super) struct InboundV2Channel { pub context: DualFundingChannelContext, } -impl InboundV1Channel {} +impl InboundV2Channel { + /// 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_from_req( + fee_estimator: &LowerBoundedFeeEstimator, entropy_source: &ES, signer_provider: &SP, + counterparty_node_id: PublicKey, our_supported_features: &ChannelTypeFeatures, + their_features: &InitFeatures, msg: &msgs::OpenChannelV2, funding_satoshis: u64, + funding_inputs:Vec, change_script_pubkey: Script, user_id: u128, + config: &UserConfig, current_chain_height: u32, logger: &L, outbound_scid_alias: u64, + require_confirmed_inputs: bool, + ) -> Result, ChannelError> + where ES::Target: EntropySource, + SP::Target: SignerProvider, + F::Target: FeeEstimator, + L::Target: Logger, + { + let announced_channel = if (msg.channel_flags & 1) == 1 { true } else { false }; + + // First check the channel type is known, failing before we do anything else if we don't + // support this channel type. + let channel_type = if let Some(channel_type) = &msg.channel_type { + if channel_type.supports_any_optional_bits() { + return Err(ChannelError::Close("Channel Type field contained optional bits - this is not allowed".to_owned())); + } + + // We only support the channel types defined by the `ChannelManager` in + // `provided_channel_type_features`. The channel type must always support + // `static_remote_key`. + if !channel_type.requires_static_remote_key() { + return Err(ChannelError::Close("Channel Type was not understood - we require static remote key".to_owned())); + } + // Make sure we support all of the features behind the channel type. + if !channel_type.is_subset(our_supported_features) { + return Err(ChannelError::Close("Channel Type contains unsupported features".to_owned())); + } + if channel_type.requires_scid_privacy() && announced_channel { + return Err(ChannelError::Close("SCID Alias/Privacy Channel Type cannot be set on a public channel".to_owned())); + } + channel_type.clone() + } else { + let channel_type = ChannelTypeFeatures::from_init(&their_features); + if channel_type != ChannelTypeFeatures::only_static_remote_key() { + return Err(ChannelError::Close("Only static_remote_key is supported for non-negotiated channel types".to_owned())); + } + channel_type + }; + let opt_anchors = channel_type.supports_anchors_zero_fee_htlc_tx(); + + let channel_keys_id = signer_provider.generate_channel_keys_id(true, msg.funding_satoshis, user_id); + let holder_signer = signer_provider.derive_channel_signer(msg.funding_satoshis, channel_keys_id); + let pubkeys = holder_signer.pubkeys().clone(); + let counterparty_pubkeys = ChannelPublicKeys { + funding_pubkey: msg.funding_pubkey, + revocation_basepoint: msg.revocation_basepoint, + payment_point: msg.payment_basepoint, + delayed_payment_basepoint: msg.delayed_payment_basepoint, + htlc_basepoint: msg.htlc_basepoint + }; + + if config.channel_handshake_config.our_to_self_delay < BREAKDOWN_TIMEOUT { + return Err(ChannelError::Close(format!("Configured with an unreasonable our_to_self_delay ({}) putting user funds at risks. It must be greater than {}", config.channel_handshake_config.our_to_self_delay, BREAKDOWN_TIMEOUT))); + } + + let channel_value_satoshis = funding_satoshis.saturating_add(msg.funding_satoshis); + + // Check sanity of message fields: + if channel_value_satoshis > config.channel_handshake_limits.max_funding_satoshis { + return Err(ChannelError::Close(format!( + "Per our config, funding must be at most {}. It was {}. Peer contribution: {}. Our contribution: {}", + config.channel_handshake_limits.max_funding_satoshis, channel_value_satoshis, + msg.funding_satoshis, funding_satoshis))); + } + if msg.funding_satoshis >= TOTAL_BITCOIN_SUPPLY_SATOSHIS { + return Err(ChannelError::Close(format!("Funding must be smaller than the total bitcoin supply. It was {}", channel_value_satoshis))); + } + if msg.dust_limit_satoshis > channel_value_satoshis { + return Err(ChannelError::Close(format!("dust_limit_satoshis {} was larger than funding_satoshis {}. Peer never wants payout outputs?", msg.dust_limit_satoshis, msg.funding_satoshis))); + } + + let counterparty_selected_channel_reserve_satoshis = Self::get_v2_channel_reserve_satoshis( + channel_value_satoshis, msg.dust_limit_satoshis); + let holder_selected_channel_reserve_satoshis = Self::get_v2_channel_reserve_satoshis( + channel_value_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS); + let full_channel_value_msat = + (channel_value_satoshis - counterparty_selected_channel_reserve_satoshis) * 1000; + if msg.htlc_minimum_msat >= full_channel_value_msat { + return Err(ChannelError::Close(format!("Minimum htlc value ({}) was larger than full channel value ({})", msg.htlc_minimum_msat, full_channel_value_msat))); + } + Channel::::check_remote_fee(fee_estimator, msg.commitment_feerate_sat_per_1000_weight, None, logger)?; + // TODO(dual_funding): Checks for `funding_feerate_sat_per_1000_weight`? + + let max_counterparty_selected_contest_delay = u16::min(config.channel_handshake_limits.their_to_self_delay, MAX_LOCAL_BREAKDOWN_TIMEOUT); + if msg.to_self_delay > max_counterparty_selected_contest_delay { + return Err(ChannelError::Close(format!("They wanted our payments to be delayed by a needlessly long period. Upper limit: {}. Actual: {}", max_counterparty_selected_contest_delay, msg.to_self_delay))); + } + if msg.max_accepted_htlcs < 1 { + return Err(ChannelError::Close("0 max_accepted_htlcs makes for a useless channel".to_owned())); + } + if msg.max_accepted_htlcs > MAX_HTLCS { + return Err(ChannelError::Close(format!("max_accepted_htlcs was {}. It must not be larger than {}", msg.max_accepted_htlcs, MAX_HTLCS))); + } + + // Now check against optional parameters as set by config... + if msg.funding_satoshis < config.channel_handshake_limits.min_funding_satoshis { + return Err(ChannelError::Close(format!( + "Funding satoshis ({}) is less than the user specified limit ({})", msg.funding_satoshis, + config.channel_handshake_limits.min_funding_satoshis))); + } + if msg.htlc_minimum_msat > config.channel_handshake_limits.max_htlc_minimum_msat { + return Err(ChannelError::Close(format!( + "htlc_minimum_msat ({}) is higher than the user specified limit ({})", + msg.htlc_minimum_msat, config.channel_handshake_limits.max_htlc_minimum_msat))); + } + if msg.max_htlc_value_in_flight_msat < config.channel_handshake_limits.min_max_htlc_value_in_flight_msat { + return Err(ChannelError::Close(format!( + "max_htlc_value_in_flight_msat ({}) is less than the user specified limit ({})", + msg.max_htlc_value_in_flight_msat, + config.channel_handshake_limits.min_max_htlc_value_in_flight_msat))); + } + if counterparty_selected_channel_reserve_satoshis > config.channel_handshake_limits.max_channel_reserve_satoshis { + return Err(ChannelError::Close(format!( + "channel_reserve_satoshis ({}) is higher than the user specified limit ({})", + counterparty_selected_channel_reserve_satoshis, + config.channel_handshake_limits.max_channel_reserve_satoshis))); + } + if msg.max_accepted_htlcs < config.channel_handshake_limits.min_max_accepted_htlcs { + return Err(ChannelError::Close(format!("max_accepted_htlcs ({}) is less than the user specified limit ({})", msg.max_accepted_htlcs, config.channel_handshake_limits.min_max_accepted_htlcs))); + } + if msg.dust_limit_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { + return Err(ChannelError::Close(format!("dust_limit_satoshis ({}) is less than the implementation limit ({})", msg.dust_limit_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS))); + } + if msg.dust_limit_satoshis > MAX_CHAN_DUST_LIMIT_SATOSHIS { + return Err(ChannelError::Close(format!("dust_limit_satoshis ({}) is greater than the implementation limit ({})", msg.dust_limit_satoshis, MAX_CHAN_DUST_LIMIT_SATOSHIS))); + } + + // Convert things into internal flags and prep our state: + + if config.channel_handshake_limits.force_announced_channel_preference { + if config.channel_handshake_config.announced_channel != announced_channel { + return Err(ChannelError::Close("Peer tried to open channel but their announcement preference is different from ours".to_owned())); + } + } + + if holder_selected_channel_reserve_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { + // Protocol level safety check in place, although it should never happen because + // of `MIN_THEIR_CHAN_RESERVE_SATOSHIS` + return Err(ChannelError::Close(format!("Suitable channel reserve not found. remote_channel_reserve was ({}). dust_limit_satoshis is ({}).", holder_selected_channel_reserve_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS))); + } + if holder_selected_channel_reserve_satoshis * 1000 >= full_channel_value_msat { + return Err(ChannelError::Close(format!("Suitable channel reserve not found. remote_channel_reserve was ({})msats. Channel value is ({})msats.", holder_selected_channel_reserve_satoshis * 1000, full_channel_value_msat))); + } + if counterparty_selected_channel_reserve_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { + log_debug!(logger, "channel_reserve_satoshis ({}) is smaller than our dust limit ({}). We can broadcast stale states without any risk, implying this channel is very insecure for our counterparty.", + counterparty_selected_channel_reserve_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS); + } + if holder_selected_channel_reserve_satoshis < msg.dust_limit_satoshis { + return Err(ChannelError::Close(format!("Dust limit ({}) too high for the channel reserve we require the remote to keep ({})", msg.dust_limit_satoshis, holder_selected_channel_reserve_satoshis))); + } + + // check if the funder's amount for the initial commitment tx is sufficient + // for full fee payment plus a few HTLCs to ensure the channel will be useful. + let funders_amount_msat = msg.funding_satoshis; + let commitment_tx_fee = Self::commit_tx_fee_msat(msg.commitment_feerate_sat_per_1000_weight, + MIN_AFFORDABLE_HTLC_COUNT, opt_anchors) / 1000; + if funders_amount_msat / 1000 < commitment_tx_fee { + return Err(ChannelError::Close(format!("Funding amount ({} sats) can't even pay fee for initial commitment transaction fee of {} sats.", funders_amount_msat / 1000, commitment_tx_fee))); + } + + let to_remote_satoshis = funders_amount_msat / 1000 - commitment_tx_fee; + // While it's reasonable for us to not meet the channel reserve initially (if they don't + // want to push much to us), our counterparty should always have more than our reserve. + if to_remote_satoshis < holder_selected_channel_reserve_satoshis { + return Err(ChannelError::Close("Insufficient funding amount for initial reserve".to_owned())); + } + + let counterparty_shutdown_scriptpubkey = if their_features.supports_upfront_shutdown_script() { + match &msg.shutdown_scriptpubkey { + &Some(ref script) => { + // Peer is signaling upfront_shutdown and has opt-out with a 0-length script. We don't enforce anything + if script.len() == 0 { + None + } else { + if !script::is_bolt2_compliant(&script, their_features) { + return Err(ChannelError::Close(format!("Peer is signaling upfront_shutdown but has provided an unacceptable scriptpubkey format: {}", script))) + } + Some(script.clone()) + } + }, + // Peer is signaling upfront shutdown but don't opt-out with correct mechanism (a.k.a 0-length script). Peer looks buggy, we fail the channel + &None => { + return Err(ChannelError::Close("Peer is signaling upfront_shutdown but we don't get any script. Use 0-length script to opt-out".to_owned())); + } + } + } else { None }; + + let shutdown_scriptpubkey = if config.channel_handshake_config.commit_upfront_shutdown_pubkey { + match signer_provider.get_shutdown_scriptpubkey() { + Ok(scriptpubkey) => Some(scriptpubkey), + Err(_) => return Err(ChannelError::Close("Failed to get upfront shutdown scriptpubkey".to_owned())), + } + } else { None }; + + if let Some(shutdown_scriptpubkey) = &shutdown_scriptpubkey { + if !shutdown_scriptpubkey.is_compatible(&their_features) { + return Err(ChannelError::Close(format!("Provided a scriptpubkey format not accepted by peer: {}", shutdown_scriptpubkey))); + } + } + + let destination_script = match signer_provider.get_destination_script() { + Ok(script) => script, + Err(_) => return Err(ChannelError::Close("Failed to get destination script".to_owned())), + }; + + let mut secp_ctx = Secp256k1::new(); + secp_ctx.seeded_randomize(&entropy_source.get_secure_random_bytes()); + + let chan = Self { + context: ChannelContext { + user_id, + + config: LegacyChannelConfig { + options: config.channel_config.clone(), + announced_channel, + commit_upfront_shutdown_pubkey: config.channel_handshake_config.commit_upfront_shutdown_pubkey, + }, + + prev_config: None, + + inbound_handshake_limits_override: None, + + temporary_channel_id: Some(msg.temporary_channel_id), + channel_id: msg.temporary_channel_id, + channel_state: (ChannelState::OurInitSent as u32) | (ChannelState::TheirInitSent as u32), + announcement_sigs_state: AnnouncementSigsState::NotSent, + secp_ctx, + + latest_monitor_update_id: 0, + + holder_signer, + shutdown_scriptpubkey, + destination_script, + + cur_holder_commitment_transaction_number: INITIAL_COMMITMENT_NUMBER, + cur_counterparty_commitment_transaction_number: INITIAL_COMMITMENT_NUMBER, + value_to_self_msat: msg.push_msat, + + pending_inbound_htlcs: Vec::new(), + pending_outbound_htlcs: Vec::new(), + holding_cell_htlc_updates: Vec::new(), + pending_update_fee: None, + holding_cell_update_fee: None, + next_holder_htlc_id: 0, + next_counterparty_htlc_id: 0, + update_time_counter: 1, + + resend_order: RAACommitmentOrder::CommitmentFirst, + + monitor_pending_channel_ready: false, + monitor_pending_revoke_and_ack: false, + monitor_pending_commitment_signed: false, + monitor_pending_forwards: Vec::new(), + monitor_pending_failures: Vec::new(), + monitor_pending_finalized_fulfills: Vec::new(), + + #[cfg(debug_assertions)] + holder_max_commitment_tx_output: Mutex::new((msg.push_msat, msg.funding_satoshis * 1000 - msg.push_msat)), + #[cfg(debug_assertions)] + counterparty_max_commitment_tx_output: Mutex::new((msg.push_msat, msg.funding_satoshis * 1000 - msg.push_msat)), + + last_sent_closing_fee: None, + pending_counterparty_closing_signed: None, + closing_fee_limits: None, + target_closing_feerate_sats_per_kw: None, + + inbound_awaiting_accept: true, + + funding_tx_confirmed_in: None, + funding_tx_confirmation_height: 0, + short_channel_id: None, + channel_creation_height: current_chain_height, + + feerate_per_kw: msg.feerate_per_kw, + channel_value_satoshis: msg.funding_satoshis, + counterparty_dust_limit_satoshis: msg.dust_limit_satoshis, + holder_dust_limit_satoshis: MIN_CHAN_DUST_LIMIT_SATOSHIS, + counterparty_max_htlc_value_in_flight_msat: cmp::min(msg.max_htlc_value_in_flight_msat, msg.funding_satoshis * 1000), + holder_max_htlc_value_in_flight_msat: Self::get_holder_max_htlc_value_in_flight_msat(msg.funding_satoshis, &config.channel_handshake_config), + counterparty_selected_channel_reserve_satoshis: Some(msg.channel_reserve_satoshis), + holder_selected_channel_reserve_satoshis, + counterparty_htlc_minimum_msat: msg.htlc_minimum_msat, + holder_htlc_minimum_msat: if config.channel_handshake_config.our_htlc_minimum_msat == 0 { 1 } else { config.channel_handshake_config.our_htlc_minimum_msat }, + counterparty_max_accepted_htlcs: msg.max_accepted_htlcs, + holder_max_accepted_htlcs: cmp::min(config.channel_handshake_config.our_max_accepted_htlcs, MAX_HTLCS), + minimum_depth: Some(cmp::max(config.channel_handshake_config.minimum_depth, 1)), + + counterparty_forwarding_info: None, + + channel_transaction_parameters: ChannelTransactionParameters { + holder_pubkeys: pubkeys, + holder_selected_contest_delay: config.channel_handshake_config.our_to_self_delay, + is_outbound_from_holder: false, + counterparty_parameters: Some(CounterpartyChannelTransactionParameters { + selected_contest_delay: msg.to_self_delay, + pubkeys: counterparty_pubkeys, + }), + funding_outpoint: None, + opt_anchors: if opt_anchors { Some(()) } else { None }, + opt_non_zero_fee_anchors: None + }, + funding_transaction: None, + + counterparty_cur_commitment_point: Some(msg.first_per_commitment_point), + counterparty_prev_commitment_point: None, + counterparty_node_id, + + counterparty_shutdown_scriptpubkey, + + commitment_secrets: CounterpartyCommitmentSecrets::new(), + + channel_update_status: ChannelUpdateStatus::Enabled, + closing_signed_in_flight: false, + + announcement_sigs: None, + + #[cfg(any(test, fuzzing))] + next_local_commitment_tx_fee_info_cached: Mutex::new(None), + #[cfg(any(test, fuzzing))] + next_remote_commitment_tx_fee_info_cached: Mutex::new(None), + + workaround_lnd_bug_4006: None, + + latest_inbound_scid_alias: None, + outbound_scid_alias, + + channel_pending_event_emitted: false, + channel_ready_event_emitted: false, + + #[cfg(any(test, fuzzing))] + historical_inbound_htlc_fulfills: HashSet::new(), + + channel_type, + channel_keys_id, + + pending_monitor_updates: Vec::new(), + } + }; + + 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, user_id: u128) -> msgs::AcceptChannelV2 { + if self.context.common.channel_state != + (ChannelState::OurInitSent as u32) | (ChannelState::TheirInitSent as u32) { + panic!("Tried to send accept_channel2 after channel had moved forward"); + } + if self.context.common.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER { + panic!("Tried to send an accept_channel2 for a channel that has already advanced"); + } + if !self.context.common.inbound_awaiting_accept { + panic!("The inbound channel has already been accepted"); + } + + self.context.common.user_id = user_id; + self.context.common.inbound_awaiting_accept = false; + + 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.common.holder_signer.get_per_commitment_point( + self.context.common.cur_holder_commitment_transaction_number, &self.context.common.secp_ctx); + let second_per_commitment_point = self.context.common.holder_signer.get_per_commitment_point( + self.context.common.cur_holder_commitment_transaction_number - 1, &self.context.common.secp_ctx); + let keys = self.get_holder_pubkeys(); + + msgs::AcceptChannelV2 { + temporary_channel_id: self.context.common.channel_id, + funding_satoshis: self.context.our_funding_satoshis, + dust_limit_satoshis: self.context.common.holder_dust_limit_satoshis, + max_htlc_value_in_flight_msat: self.context.common.holder_max_htlc_value_in_flight_msat, + htlc_minimum_msat: self.context.common.holder_htlc_minimum_msat, + minimum_depth: self.context.common.minimum_depth.unwrap(), + to_self_delay: self.get_holder_selected_contest_delay(), + max_accepted_htlcs: self.context.common.holder_max_accepted_htlcs, + funding_pubkey: keys.funding_pubkey, + revocation_basepoint: keys.revocation_basepoint, + payment_basepoint: keys.payment_point, + delayed_payment_basepoint: keys.delayed_payment_basepoint, + htlc_basepoint: keys.htlc_basepoint, + first_per_commitment_point, + second_per_commitment_point, + shutdown_scriptpubkey: Some(match &self.context.common.shutdown_scriptpubkey { + Some(script) => script.clone().into_inner(), + None => Builder::new().into_script(), + }), + channel_type: Some(self.context.common.channel_type.clone()), + require_confirmed_inputs: if self.context.we_require_confirmed_inputs { Some(()) } else { 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() + } +} // Prefunded channel utilities @@ -6767,8 +7209,11 @@ fn get_initial_channel_type(config: &UserConfig, their_features: &InitFeatures) ret } -/// Generates a temporary_channel_id for a V2 channel -fn generate_v2_temporary_channel_id( +/// Generates a `channel_id` for a V2-established channel. +/// +/// If you wish to generate a `temporary_channel_id` to send in an `open_channel2` message, then +/// let `their_revocation_basepoint` be the zero point. +fn generate_v2_channel_id( our_revocation_basepoint: &PublicKey, their_revocation_basepoint: &PublicKey, ) -> [u8; 32] {