@@ -28,7 +28,7 @@ use bitcoin::hash_types::{BlockHash, Txid};
28
28
29
29
use bitcoin::secp256k1::{SecretKey,PublicKey};
30
30
use bitcoin::secp256k1::Secp256k1;
31
- use bitcoin:: { LockTime , secp256k1, Sequence } ;
31
+ use bitcoin::{LockTime, secp256k1, Sequence, Script };
32
32
33
33
use crate::chain;
34
34
use crate::chain::{Confirm, ChannelMonitorUpdateStatus, Watch, BestBlock};
@@ -80,6 +80,9 @@ use core::ops::Deref;
80
80
pub use crate::ln::outbound_payment::{PaymentSendFailure, Retry, RetryableSendFailure, RecipientOnionFields};
81
81
use crate::ln::script::ShutdownScript;
82
82
83
+ // Re-export this for use in the public API.
84
+ pub use super::channel::DualFundingUtxo;
85
+
83
86
// We hold various information about HTLC relay in the HTLC objects in Channel itself:
84
87
//
85
88
// Upon receipt of an HTLC from a peer, we'll give it a PendingHTLCStatus indicating if it should
@@ -2250,6 +2253,106 @@ where
2250
2253
Ok(temporary_channel_id)
2251
2254
}
2252
2255
2256
+
2257
+ /// Creates a new outbound dual-funded channel to the given remote node and with the given value
2258
+ /// contributed by us.
2259
+ ///
2260
+ /// `user_channel_id` will be provided back as in
2261
+ /// [`Event::FundingGenerationReady::user_channel_id`] to allow tracking of which events
2262
+ /// correspond with which `create_channel` call. Note that the `user_channel_id` defaults to a
2263
+ /// randomized value for inbound channels. `user_channel_id` has no meaning inside of LDK, it
2264
+ /// is simply copied to events and otherwise ignored.
2265
+ ///
2266
+ /// `funnding_satoshis` is the amount we are contributing to the channel.
2267
+ /// Raises [`APIError::APIMisuseError`] when `funding_satoshis` > 2**24.
2268
+ ///
2269
+ /// The `funding_inputs` parameter accepts UTXOs in the form of [`DualFundingUtxo`] which will
2270
+ /// be used to contribute `funding_satoshis` towards the channel (minus any mining fees due).
2271
+ /// Raises [`APIError::APIMisuseError`] if the total value of the provided `funding_inputs` is
2272
+ /// less than `funding_satoshis`.
2273
+ // TODO(dual_funding): Describe error relating to inputs not being able to cover fees payable by us.
2274
+ ///
2275
+ /// The `change_script_pubkey` parameter provides a destination for the change output if any value
2276
+ /// is remaining (greater than dust) after `funding_satoshis` and fees payable are satisfied by
2277
+ /// `funding_inputs`
2278
+ // TODO(dual_funding): We could allow a list of such outputs to be provided so that the user may
2279
+ /// be able to do some more interesting things at the same time as funding a channel, like making
2280
+ /// some low priority on-chain payment.
2281
+ ///
2282
+ /// The `funding_conf_target` parameter sets the priority of the funding transaction for appropriate
2283
+ /// fee estimation. If `None`, then [`ConfirmationTarget::Normal`] is used.
2284
+ ///
2285
+ /// Raises [`APIError::ChannelUnavailable`] if the channel cannot be opened due to failing to
2286
+ /// generate a shutdown scriptpubkey or destination script set by
2287
+ /// [`SignerProvider::get_shutdown_scriptpubkey`] or [`SignerProvider::get_destination_script`].
2288
+ ///
2289
+ /// Note that we do not check if you are currently connected to the given peer. If no
2290
+ /// connection is available, the outbound `open_channel` message may fail to send, resulting in
2291
+ /// the channel eventually being silently forgotten (dropped on reload).
2292
+ ///
2293
+ /// Returns the new Channel's temporary `channel_id`. This ID will appear as
2294
+ /// [`Event::FundingGenerationReady::temporary_channel_id`] and in
2295
+ /// [`ChannelDetails::channel_id`] until after
2296
+ /// [`ChannelManager::funding_transaction_generated`] is called, swapping the Channel's ID for
2297
+ /// one derived from the funding transaction's TXID. If the counterparty rejects the channel
2298
+ /// immediately, this temporary ID will appear in [`Event::ChannelClosed::channel_id`].
2299
+ ///
2300
+ /// [`Event::FundingGenerationReady::user_channel_id`]: events::Event::FundingGenerationReady::user_channel_id
2301
+ /// [`Event::FundingGenerationReady::temporary_channel_id`]: events::Event::FundingGenerationReady::temporary_channel_id
2302
+ /// [`Event::ChannelClosed::channel_id`]: events::Event::ChannelClosed::channel_id
2303
+ /// [`ConfirmationTarget::Normal`]: chain::chaininterface::ConfirmationTarget
2304
+ pub fn create_dual_funded_channel(&self, their_network_key: PublicKey, funding_satoshis: u64,
2305
+ funding_inputs: Vec<DualFundingUtxo>, change_script_pubkey: Script, funding_conf_target: Option<ConfirmationTarget>,
2306
+ user_channel_id: u128, override_config: Option<UserConfig>) -> Result<[u8; 32], APIError>
2307
+ {
2308
+ Self::dual_funding_amount_checks(funding_satoshis, &funding_inputs)?;
2309
+
2310
+ let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
2311
+ // We want to make sure the lock is actually acquired by PersistenceNotifierGuard.
2312
+ debug_assert!(&self.total_consistency_lock.try_write().is_err());
2313
+
2314
+ let per_peer_state = self.per_peer_state.read().unwrap();
2315
+
2316
+ let peer_state_mutex = per_peer_state.get(&their_network_key)
2317
+ .ok_or_else(|| APIError::APIMisuseError{ err: format!("Not connected to node: {}", their_network_key) })?;
2318
+
2319
+ let mut peer_state = peer_state_mutex.lock().unwrap();
2320
+ let channel = {
2321
+ let outbound_scid_alias = self.create_and_insert_outbound_scid_alias();
2322
+ let their_features = &peer_state.latest_features;
2323
+ let config = if override_config.is_some() { override_config.as_ref().unwrap() } else { &self.default_configuration };
2324
+ match OutboundV2Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, their_network_key,
2325
+ their_features, funding_satoshis, funding_inputs, change_script_pubkey, user_channel_id, config,
2326
+ self.best_block.read().unwrap().height(), outbound_scid_alias, true, funding_conf_target)
2327
+ {
2328
+ Ok(res) => res,
2329
+ Err(e) => {
2330
+ self.outbound_scid_aliases.lock().unwrap().remove(&outbound_scid_alias);
2331
+ return Err(e);
2332
+ },
2333
+ }
2334
+ };
2335
+ let res = channel.get_open_channel_v2(self.genesis_hash.clone());
2336
+
2337
+ let temporary_channel_id = channel.context.common.channel_id();
2338
+ match peer_state.outbound_v2_channel_by_id.entry(temporary_channel_id) {
2339
+ hash_map::Entry::Occupied(_) => {
2340
+ if cfg!(fuzzing) {
2341
+ return Err(APIError::APIMisuseError { err: "Fuzzy bad RNG".to_owned() });
2342
+ } else {
2343
+ panic!("RNG is bad???");
2344
+ }
2345
+ },
2346
+ hash_map::Entry::Vacant(entry) => { entry.insert(channel); }
2347
+ }
2348
+
2349
+ peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannelV2 {
2350
+ node_id: their_network_key,
2351
+ msg: res,
2352
+ });
2353
+ Ok(temporary_channel_id)
2354
+ }
2355
+
2253
2356
fn list_funded_channels_with_filter<Fn: FnMut(&(&[u8; 32], &Channel<<SP::Target as SignerProvider>::Signer>)) -> bool + Copy>(&self, f: Fn) -> Vec<ChannelDetails> {
2254
2357
// Allocate our best estimate of the number of channels we have in the `res`
2255
2358
// Vec. Sadly the `short_to_chan_info` map doesn't cover channels without
@@ -5172,6 +5275,130 @@ where
5172
5275
Ok(())
5173
5276
}
5174
5277
5278
+ /// Accepts a request to open a dual-funded channel after an [`Event::OpenChannelV2Request`].
5279
+ ///
5280
+ /// The `temporary_channel_id` parameter indicates which inbound channel should be accepted,
5281
+ /// and the `counterparty_node_id` parameter is the id of the peer which has requested to open
5282
+ /// the channel.
5283
+ ///
5284
+ /// The `user_channel_id` parameter will be provided back in
5285
+ /// [`Event::ChannelClosed::user_channel_id`] to allow tracking of which events correspond
5286
+ /// with which `accept_inbound_dual_funded_channel`/`accept_inbound_dual_funded_channel_from_trusted_peer_0conf` call.
5287
+ ///
5288
+ /// `funnding_satoshis` is the amount we are contributing to the channel.
5289
+ /// Raises [`APIError::APIMisuseError`] when `funding_satoshis` > 2**24.
5290
+ ///
5291
+ /// The `funding_inputs` parameter accepts UTXOs in the form of [`DualFundingUtxo`] which will
5292
+ /// be used to contribute `funding_satoshis` towards the channel (minus any mining fees due).
5293
+ /// Raises [`APIError::APIMisuseError`] if the total value of the provided `funding_inputs` is
5294
+ /// less than `funding_satoshis`.
5295
+ // TODO(dual_funding): Describe error relating to inputs not being able to cover fees payable by us.
5296
+ ///
5297
+ /// The `change_script_pubkey` parameter provides a destination for the change output if any value
5298
+ /// is remaining (greater than dust) after `funding_satoshis` and fees payable are satisfied by
5299
+ /// `funding_inputs`
5300
+ // TODO(dual_funding): We could allow a list of such outputs to be provided so that the user may
5301
+ /// be able to do some more interesting things at the same time as funding.
5302
+
5303
+ /// Note that this method will return an error and reject the channel, if it requires support
5304
+ /// for zero confirmations.
5305
+ // TODO(dual_funding): Discussion on complications with 0conf dual-funded channels where "locking"
5306
+ // of UTXOs used for funding would be required and other issues.
5307
+ // See: https://lists.linuxfoundation.org/pipermail/lightning-dev/2023-May/003920.html
5308
+ ///
5309
+ ///
5310
+ /// [`Event::OpenChannelV2Request`]: events::Event::OpenChannelV2Request
5311
+ /// [`Event::ChannelClosed::user_channel_id`]: events::Event::ChannelClosed::user_channel_id
5312
+ pub fn accept_inbound_dual_funded_channel(&self, temporary_channel_id: &[u8; 32],
5313
+ counterparty_node_id: &PublicKey, user_channel_id: u128, funding_satoshis: u64,
5314
+ funding_inputs: Vec<DualFundingUtxo>, change_script_pubkey: Script) -> Result<(), APIError> {
5315
+ self.do_accept_inbound_dual_funded_channel(temporary_channel_id, counterparty_node_id,
5316
+ user_channel_id, funding_satoshis, funding_inputs, change_script_pubkey)
5317
+ }
5318
+
5319
+ fn do_accept_inbound_dual_funded_channel(&self, temporary_channel_id: &[u8; 32],
5320
+ counterparty_node_id: &PublicKey, user_channel_id: u128, funding_satoshis: u64,
5321
+ funding_inputs: Vec<DualFundingUtxo>, change_script_pubkey: Script,
5322
+ ) -> Result<(), APIError> {
5323
+ Self::dual_funding_amount_checks(funding_satoshis, &funding_inputs)?;
5324
+
5325
+ let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
5326
+
5327
+ let peers_without_funded_channels =
5328
+ self.peers_without_funded_channels(|peer| { peer.total_channel_count() > 0 });
5329
+ let per_peer_state = self.per_peer_state.read().unwrap();
5330
+ let peer_state_mutex = per_peer_state.get(counterparty_node_id)
5331
+ .ok_or_else(|| APIError::ChannelUnavailable { err: format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id) })?;
5332
+ let mut peer_state_lock = peer_state_mutex.lock().unwrap();
5333
+ let peer_state = &mut *peer_state_lock;
5334
+ let is_only_peer_channel = peer_state.total_channel_count() == 1;
5335
+ match peer_state.inbound_v2_channel_by_id.entry(temporary_channel_id.clone()) {
5336
+ hash_map::Entry::Occupied(mut channel) => {
5337
+ if !channel.get().is_awaiting_accept() {
5338
+ return Err(APIError::APIMisuseError { err: "The channel isn't currently awaiting to be accepted.".to_owned() });
5339
+ }
5340
+ // TODO(dual_funding): Accept zero-conf dual-funded channels.
5341
+ // See: https://lists.linuxfoundation.org/pipermail/lightning-dev/2023-May/003920.html
5342
+ if channel.get().context.common.get_channel_type().requires_zero_conf() {
5343
+ let send_msg_err_event = events::MessageSendEvent::HandleError {
5344
+ node_id: channel.get().context.common.get_counterparty_node_id(),
5345
+ action: msgs::ErrorAction::SendErrorMessage{
5346
+ msg: msgs::ErrorMessage { channel_id: temporary_channel_id.clone(), data: "No zero confirmation channels accepted".to_owned(), }
5347
+ }
5348
+ };
5349
+ peer_state.pending_msg_events.push(send_msg_err_event);
5350
+ let _ = remove_channel!(self, channel);
5351
+ return Err(APIError::APIMisuseError { err: "Zero-conf dual-funded channels are not yet accepted.".to_owned() });
5352
+ } else {
5353
+ // If this peer already has some channels, a new channel won't increase our number of peers
5354
+ // with unfunded channels, so as long as we aren't over the maximum number of unfunded
5355
+ // channels per-peer we can accept channels from a peer with existing ones.
5356
+ if is_only_peer_channel && peers_without_funded_channels >= MAX_UNFUNDED_CHANNEL_PEERS {
5357
+ let send_msg_err_event = events::MessageSendEvent::HandleError {
5358
+ node_id: channel.get().context.common.get_counterparty_node_id(),
5359
+ action: msgs::ErrorAction::SendErrorMessage{
5360
+ msg: msgs::ErrorMessage { channel_id: temporary_channel_id.clone(), data: "Have too many peers with unfunded channels, not accepting new ones".to_owned(), }
5361
+ }
5362
+ };
5363
+ peer_state.pending_msg_events.push(send_msg_err_event);
5364
+ let _ = remove_channel!(self, channel);
5365
+ return Err(APIError::APIMisuseError { err: "Too many peers with unfunded channels, refusing to accept new ones".to_owned() });
5366
+ }
5367
+ }
5368
+
5369
+ channel.get_mut().context.our_change_script_pubkey = change_script_pubkey;
5370
+
5371
+ peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannelV2 {
5372
+ node_id: channel.get().context.common.get_counterparty_node_id(),
5373
+ msg: channel.get_mut().accept_inbound_dual_funded_channel(user_channel_id),
5374
+ });
5375
+ }
5376
+ hash_map::Entry::Vacant(_) => {
5377
+ return Err(APIError::ChannelUnavailable { err: format!("Channel with id {} not found for the passed counterparty node_id {}", log_bytes!(*temporary_channel_id), counterparty_node_id) });
5378
+ }
5379
+ }
5380
+ Ok(())
5381
+ }
5382
+
5383
+ /// Checks related to inputs and their amounts related to establishing dual-funded channels.
5384
+ fn dual_funding_amount_checks(funding_satoshis: u64, funding_inputs: &Vec<DualFundingUtxo>)
5385
+ -> Result<(), APIError> {
5386
+ if funding_satoshis < 1000 {
5387
+ return Err(APIError::APIMisuseError {
5388
+ err: format!("Funding amount must be at least 1000 satoshis. It was {} sats", funding_satoshis),
5389
+ });
5390
+ }
5391
+
5392
+ let total_input_satoshis: u64 = funding_inputs.iter().map(|input| input.output.value).sum();
5393
+ if total_input_satoshis < funding_satoshis {
5394
+ Err(APIError::APIMisuseError {
5395
+ err: format!("Total value of funding inputs must be at least funding amount. It was {} sats",
5396
+ total_input_satoshis) })
5397
+ } else {
5398
+ Ok(())
5399
+ }
5400
+ }
5401
+
5175
5402
/// Gets the number of peers which match the given filter and do not have any funded, outbound,
5176
5403
/// or 0-conf channels.
5177
5404
///
@@ -5341,6 +5568,38 @@ where
5341
5568
Ok(())
5342
5569
}
5343
5570
5571
+ fn internal_accept_channel_v2(&self, counterparty_node_id: &PublicKey, msg: &msgs::AcceptChannelV2) -> Result<(), MsgHandleErrInternal> {
5572
+ let per_peer_state = self.per_peer_state.read().unwrap();
5573
+ let peer_state_mutex = per_peer_state.get(counterparty_node_id)
5574
+ .ok_or_else(|| {
5575
+ debug_assert!(false);
5576
+ MsgHandleErrInternal::send_err_msg_no_close(format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), msg.temporary_channel_id)
5577
+ })?;
5578
+ let mut peer_state_lock = peer_state_mutex.lock().unwrap();
5579
+ let peer_state = &mut *peer_state_lock;
5580
+ match peer_state.outbound_v2_channel_by_id.entry(msg.temporary_channel_id) {
5581
+ hash_map::Entry::Occupied(mut chan) => {
5582
+ let res = chan.get_mut().accept_channel_v2(&msg, &self.default_configuration.channel_handshake_limits, &peer_state.latest_features);
5583
+ if let Err(err) = res {
5584
+ // If we get an error at this point, the outbound channel should just be discarded and
5585
+ // removed from maps as it's safe to do so.
5586
+ update_maps_on_chan_removal!(self, &chan.get().context());
5587
+ let user_id = chan.get().context.common.get_user_id();
5588
+ let shutdown_res = chan.get_mut().context.common.force_shutdown(false);
5589
+ chan.remove_entry();
5590
+ return Err(MsgHandleErrInternal::from_finish_shutdown(format!("{}", err),
5591
+ msg.temporary_channel_id, user_id, shutdown_res, None));
5592
+ };
5593
+ // TODO(dual_funding): Begin Interactive Transaction Construction
5594
+ // Here we will initialize the `InteractiveTxConstructor` and delegate
5595
+ // the actual message handling for the interactive transaction protocol
5596
+ // to its state machine.
5597
+ Ok(())
5598
+ },
5599
+ hash_map::Entry::Vacant(_) => Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.temporary_channel_id)),
5600
+ }
5601
+ }
5602
+
5344
5603
fn internal_funding_created(&self, counterparty_node_id: &PublicKey, msg: &msgs::FundingCreated) -> Result<(), MsgHandleErrInternal> {
5345
5604
let best_block = *self.best_block.read().unwrap();
5346
5605
@@ -7012,9 +7271,8 @@ where
7012
7271
}
7013
7272
7014
7273
fn handle_accept_channel_v2(&self, counterparty_node_id: &PublicKey, msg: &msgs::AcceptChannelV2) {
7015
- let _: Result < ( ) , _ > = handle_error ! ( self , Err ( MsgHandleErrInternal :: send_err_msg_no_close(
7016
- "Dual-funded channels not supported" . to_owned( ) ,
7017
- msg. temporary_channel_id. clone( ) ) ) , * counterparty_node_id) ;
7274
+ let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
7275
+ let _ = handle_error!(self, self.internal_accept_channel_v2(counterparty_node_id, msg), *counterparty_node_id);
7018
7276
}
7019
7277
7020
7278
fn handle_funding_created(&self, counterparty_node_id: &PublicKey, msg: &msgs::FundingCreated) {
@@ -10201,6 +10459,9 @@ mod tests {
10201
10459
_ => panic!("expected BroadcastChannelUpdate event"),
10202
10460
}
10203
10461
}
10462
+
10463
+ // Dual-funding: V2 Channel Establishment Tests
10464
+ // TODO(dual_funding): Complete these.
10204
10465
}
10205
10466
10206
10467
#[cfg(ldk_bench)]
0 commit comments