diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 73851d3c8..c80402824 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -946,6 +946,10 @@ pub const MAX_CHAN_DUST_LIMIT_SATOSHIS: u64 = MAX_STD_OUTPUT_DUST_LIMIT_SATOSHIS /// See for more details. pub const MIN_CHAN_DUST_LIMIT_SATOSHIS: u64 = 354; +pub const VIRTUAL_DUST_LIMIT_SATOSHIS: u64 = 1; + +pub const VIRTUAL_HTLC_MINIMUM_MSAT: u64 = 1000; + // Just a reasonable implementation-specific safe lower bound, higher than the dust limit. pub const MIN_THEIR_CHAN_RESERVE_SATOSHIS: u64 = 1000; @@ -3470,6 +3474,7 @@ where current_chain_height: u32, logger: &'a L, is_0conf: bool, + is_virtual: bool, our_funding_satoshis: u64, counterparty_pubkeys: ChannelPublicKeys, channel_type: ChannelTypeFeatures, @@ -3551,8 +3556,13 @@ where if open_channel_fields.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 ({})", open_channel_fields.max_accepted_htlcs, config.channel_handshake_limits.min_max_accepted_htlcs))); } - if open_channel_fields.dust_limit_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { - return Err(ChannelError::close(format!("dust_limit_satoshis ({}) is less than the implementation limit ({})", open_channel_fields.dust_limit_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS))); + let min_dust_limit_satoshis = if is_virtual { + VIRTUAL_DUST_LIMIT_SATOSHIS + } else { + MIN_CHAN_DUST_LIMIT_SATOSHIS + }; + if open_channel_fields.dust_limit_satoshis < min_dust_limit_satoshis { + return Err(ChannelError::close(format!("dust_limit_satoshis ({}) is less than the implementation limit ({})", open_channel_fields.dust_limit_satoshis, min_dust_limit_satoshis))); } if open_channel_fields.dust_limit_satoshis > MAX_CHAN_DUST_LIMIT_SATOSHIS { return Err(ChannelError::close(format!("dust_limit_satoshis ({}) is greater than the implementation limit ({})", open_channel_fields.dust_limit_satoshis, MAX_CHAN_DUST_LIMIT_SATOSHIS))); @@ -3755,11 +3765,11 @@ where feerate_per_kw: open_channel_fields.commitment_feerate_sat_per_1000_weight, counterparty_dust_limit_satoshis: open_channel_fields.dust_limit_satoshis, - holder_dust_limit_satoshis: MIN_CHAN_DUST_LIMIT_SATOSHIS, + holder_dust_limit_satoshis: if is_virtual { VIRTUAL_DUST_LIMIT_SATOSHIS } else { MIN_CHAN_DUST_LIMIT_SATOSHIS }, counterparty_max_htlc_value_in_flight_msat: cmp::min(open_channel_fields.max_htlc_value_in_flight_msat, channel_value_satoshis * 1000), holder_max_htlc_value_in_flight_msat: get_holder_max_htlc_value_in_flight_msat(channel_value_satoshis, &config.channel_handshake_config), counterparty_htlc_minimum_msat: open_channel_fields.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 }, + holder_htlc_minimum_msat: if is_virtual { VIRTUAL_HTLC_MINIMUM_MSAT } else if config.channel_handshake_config.our_htlc_minimum_msat == 0 { 1 } else { config.channel_handshake_config.our_htlc_minimum_msat }, counterparty_max_accepted_htlcs: open_channel_fields.max_accepted_htlcs, holder_max_accepted_htlcs: cmp::min(config.channel_handshake_config.our_max_accepted_htlcs, max_htlcs(&channel_type)), minimum_depth, @@ -4352,8 +4362,13 @@ where if common_fields.max_accepted_htlcs < peer_limits.min_max_accepted_htlcs { return Err(ChannelError::close(format!("max_accepted_htlcs ({}) is less than the user specified limit ({})", common_fields.max_accepted_htlcs, peer_limits.min_max_accepted_htlcs))); } - if common_fields.dust_limit_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { - return Err(ChannelError::close(format!("dust_limit_satoshis ({}) is less than the implementation limit ({})", common_fields.dust_limit_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS))); + let min_dust_limit_satoshis = if self.is_virtual_dust_set() { + VIRTUAL_DUST_LIMIT_SATOSHIS + } else { + MIN_CHAN_DUST_LIMIT_SATOSHIS + }; + if common_fields.dust_limit_satoshis < min_dust_limit_satoshis { + return Err(ChannelError::close(format!("dust_limit_satoshis ({}) is less than the implementation limit ({})", common_fields.dust_limit_satoshis, min_dust_limit_satoshis))); } if common_fields.dust_limit_satoshis > MAX_CHAN_DUST_LIMIT_SATOSHIS { return Err(ChannelError::close(format!("dust_limit_satoshis ({}) is greater than the implementation limit ({})", common_fields.dust_limit_satoshis, MAX_CHAN_DUST_LIMIT_SATOSHIS))); @@ -4480,6 +4495,10 @@ where self.trusted_no_broadcast } + pub fn is_virtual_dust_set(&self) -> bool { + self.holder_dust_limit_satoshis == VIRTUAL_DUST_LIMIT_SATOSHIS + } + pub fn get_cltv_expiry_delta(&self) -> u16 { cmp::max(self.config.options.cltv_expiry_delta, MIN_CLTV_EXPIRY_DELTA) } @@ -4607,6 +4626,10 @@ where self.trusted_no_broadcast = true; } + pub fn set_virtual_dust_limit(&mut self) { + self.holder_dust_limit_satoshis = VIRTUAL_DUST_LIMIT_SATOSHIS; + } + fn can_resume_on_reconnect(&self) -> bool { match self.channel_state { ChannelState::NegotiatingFunding(_) => false, @@ -14042,7 +14065,7 @@ where fee_estimator: &LowerBoundedFeeEstimator, entropy_source: &ES, signer_provider: &SP, counterparty_node_id: PublicKey, our_supported_features: &ChannelTypeFeatures, their_features: &InitFeatures, msg: &msgs::OpenChannel, user_id: u128, config: &UserConfig, - current_chain_height: u32, logger: &L, is_0conf: bool, ldk_data_dir: PathBuf, + current_chain_height: u32, logger: &L, is_0conf: bool, is_virtual: bool, ldk_data_dir: PathBuf, rgb_kv_store: Arc, ) -> Result, ChannelError> where ES::Target: EntropySource, @@ -14075,6 +14098,7 @@ where current_chain_height, &&logger, is_0conf, + is_virtual, 0, counterparty_pubkeys, @@ -14479,6 +14503,7 @@ where current_chain_height, logger, false, + false, our_funding_contribution_sats, counterparty_pubkeys, channel_type, diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index e639e3aa3..4b082e8b3 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -2124,7 +2124,7 @@ where /// # let channel_manager = channel_manager.get_cm(); /// let value_sats = 1_000_000; /// let push_msats = 10_000_000; -/// match channel_manager.create_channel(peer_id, value_sats, push_msats, 42, None, None) { +/// match channel_manager.create_channel(peer_id, value_sats, push_msats, 42, None, None, None, None, false) { /// Ok(channel_id) => println!("Opening channel {}", channel_id), /// Err(e) => println!("Error opening channel: {:?}", e), /// } @@ -4162,7 +4162,7 @@ where /// [`Event::FundingGenerationReady::temporary_channel_id`]: events::Event::FundingGenerationReady::temporary_channel_id /// [`Event::ChannelClosed::channel_id`]: events::Event::ChannelClosed::channel_id #[rustfmt::skip] - pub fn create_channel(&self, their_network_key: PublicKey, channel_value_satoshis: u64, push_msat: u64, user_channel_id: u128, temporary_channel_id: Option, override_config: Option, consignment_endpoint: Option, push_asset_amount: Option) -> Result { + pub fn create_channel(&self, their_network_key: PublicKey, channel_value_satoshis: u64, push_msat: u64, user_channel_id: u128, temporary_channel_id: Option, override_config: Option, consignment_endpoint: Option, push_asset_amount: Option, is_virtual: bool) -> Result { if channel_value_satoshis < 1000 { return Err(APIError::APIMisuseError { err: format!("Channel value must be at least 1000 satoshis. It was {}", channel_value_satoshis) }); } @@ -4208,6 +4208,9 @@ where }, } }; + if is_virtual { + channel.context.set_virtual_dust_limit(); + } let logger = WithChannelContext::from(&self.logger, &channel.context, None); let res = channel.get_open_channel(self.chain_hash, &&logger); @@ -6299,6 +6302,12 @@ where err: format!("Channel {temporary_channel_id} with counterparty {counterparty_node_id} is not an unfunded, outbound channel ready to fund"), }); } + if chan.get().context().is_virtual_dust_set() && !is_trusted_no_broadcast { + return Err(APIError::APIMisuseError { + err: "Channels opened with virtual dust must be completed with ChannelFundingType::Virtual" + .to_owned(), + }); + } if is_trusted_no_broadcast && chan.get().minimum_depth() != Some(0) { return Err(APIError::APIMisuseError { err: "ChannelFundingType::Virtual requires a negotiated 0-conf channel" @@ -10205,6 +10214,15 @@ 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 is_only_peer_channel = peer_state.total_channel_count() == 1; + if channel_funding_type == ChannelFundingType::Virtual { + if let Some(unaccepted_channel) = peer_state.inbound_channel_request_by_id.get(temporary_channel_id) { + if matches!(unaccepted_channel.open_channel_msg, OpenChannelMessage::V2(_)) { + return Err(APIError::APIMisuseError { + err: "ChannelFundingType::Virtual is not supported for inbound v2 channels".to_owned(), + }); + } + } + } // Find (and remove) the channel in the unaccepted table. If it's not there, something weird is // happening and return an error. N.B. that we create channel with an outbound SCID of zero so @@ -10218,7 +10236,8 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ InboundV1Channel::new( &self.fee_estimator, &self.entropy_source, &self.signer_provider, *counterparty_node_id, &self.channel_type_features(), &peer_state.latest_features, &open_channel_msg, - user_channel_id, &config, best_block_height, &self.logger, accept_0conf, self.ldk_data_dir.clone(), + user_channel_id, &config, best_block_height, &self.logger, accept_0conf, + channel_funding_type == ChannelFundingType::Virtual, self.ldk_data_dir.clone(), Arc::clone(&self.rgb_kv_store), ).map_err(|err| MsgHandleErrInternal::from_chan_no_close(err, *temporary_channel_id) ).map(|mut channel| { @@ -10513,7 +10532,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ let mut channel = InboundV1Channel::new( &self.fee_estimator, &self.entropy_source, &self.signer_provider, *counterparty_node_id, &self.channel_type_features(), &peer_state.latest_features, msg, user_channel_id, - &self.config.read().unwrap(), best_block_height, &self.logger, /*is_0conf=*/false, self.ldk_data_dir.clone(), + &self.config.read().unwrap(), best_block_height, &self.logger, /*is_0conf=*/false, false, self.ldk_data_dir.clone(), Arc::clone(&self.rgb_kv_store), ).map_err(|e| MsgHandleErrInternal::from_chan_no_close(e, msg.common_fields.temporary_channel_id))?; let logger = WithChannelContext::from(&self.logger, &channel.context, None);