Skip to content

Commit 335ab6f

Browse files
committed
wip: establishment logic
1 parent a22bd08 commit 335ab6f

File tree

2 files changed

+280
-4
lines changed

2 files changed

+280
-4
lines changed

Diff for: lightning/src/ln/channel.rs

+15
Original file line numberDiff line numberDiff line change
@@ -6833,6 +6833,21 @@ impl<Signer: WriteableEcdsaChannelSigner> OutboundV2Channel<Signer> {
68336833
require_confirmed_inputs: if self.context.we_require_confirmed_inputs { Some(()) } else { None },
68346834
}
68356835
}
6836+
6837+
pub fn accept_channel_v2(&mut self, msg: &msgs::AcceptChannelV2, default_limits: &ChannelHandshakeLimits,
6838+
their_features: &InitFeatures) -> Result<(), ChannelError> {
6839+
self.context.common.do_accept_channel_checks(
6840+
default_limits, their_features, msg.dust_limit_satoshis, get_v2_channel_reserve_satoshis(
6841+
msg.funding_satoshis + self.context.our_funding_satoshis, msg.dust_limit_satoshis),
6842+
msg.to_self_delay, msg.max_accepted_htlcs, msg.htlc_minimum_msat,
6843+
msg.max_htlc_value_in_flight_msat, msg.minimum_depth, &msg.channel_type,
6844+
&msg.shutdown_scriptpubkey, msg.funding_pubkey, msg.revocation_basepoint, msg.payment_basepoint,
6845+
msg.delayed_payment_basepoint, msg.htlc_basepoint, msg.first_per_commitment_point)?;
6846+
6847+
// TODO(dual_funding): V2 Establishment Checks
6848+
6849+
Ok(())
6850+
}
68366851
}
68376852

68386853
// A not-yet-funded inbound (from counterparty) channel using V2 channel establishment.

Diff for: lightning/src/ln/channelmanager.rs

+265-4
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use bitcoin::hash_types::{BlockHash, Txid};
2828

2929
use bitcoin::secp256k1::{SecretKey,PublicKey};
3030
use bitcoin::secp256k1::Secp256k1;
31-
use bitcoin::{LockTime, secp256k1, Sequence};
31+
use bitcoin::{LockTime, secp256k1, Sequence, Script};
3232

3333
use crate::chain;
3434
use crate::chain::{Confirm, ChannelMonitorUpdateStatus, Watch, BestBlock};
@@ -80,6 +80,9 @@ use core::ops::Deref;
8080
pub use crate::ln::outbound_payment::{PaymentSendFailure, Retry, RetryableSendFailure, RecipientOnionFields};
8181
use crate::ln::script::ShutdownScript;
8282

83+
// Re-export this for use in the public API.
84+
pub use super::channel::DualFundingUtxo;
85+
8386
// We hold various information about HTLC relay in the HTLC objects in Channel itself:
8487
//
8588
// Upon receipt of an HTLC from a peer, we'll give it a PendingHTLCStatus indicating if it should
@@ -2250,6 +2253,106 @@ where
22502253
Ok(temporary_channel_id)
22512254
}
22522255

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+
22532356
fn list_funded_channels_with_filter<Fn: FnMut(&(&[u8; 32], &Channel<<SP::Target as SignerProvider>::Signer>)) -> bool + Copy>(&self, f: Fn) -> Vec<ChannelDetails> {
22542357
// Allocate our best estimate of the number of channels we have in the `res`
22552358
// Vec. Sadly the `short_to_chan_info` map doesn't cover channels without
@@ -5172,6 +5275,130 @@ where
51725275
Ok(())
51735276
}
51745277

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+
51755402
/// Gets the number of peers which match the given filter and do not have any funded, outbound,
51765403
/// or 0-conf channels.
51775404
///
@@ -5341,6 +5568,38 @@ where
53415568
Ok(())
53425569
}
53435570

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+
53445603
fn internal_funding_created(&self, counterparty_node_id: &PublicKey, msg: &msgs::FundingCreated) -> Result<(), MsgHandleErrInternal> {
53455604
let best_block = *self.best_block.read().unwrap();
53465605

@@ -7012,9 +7271,8 @@ where
70127271
}
70137272

70147273
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);
70187276
}
70197277

70207278
fn handle_funding_created(&self, counterparty_node_id: &PublicKey, msg: &msgs::FundingCreated) {
@@ -10201,6 +10459,9 @@ mod tests {
1020110459
_ => panic!("expected BroadcastChannelUpdate event"),
1020210460
}
1020310461
}
10462+
10463+
// Dual-funding: V2 Channel Establishment Tests
10464+
// TODO(dual_funding): Complete these.
1020410465
}
1020510466

1020610467
#[cfg(ldk_bench)]

0 commit comments

Comments
 (0)