-
Notifications
You must be signed in to change notification settings - Fork 407
Support manual signaling of channel readiness #2109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1449,6 +1449,22 @@ macro_rules! remove_channel { | |
} | ||
} | ||
|
||
macro_rules! channel_ready_pending { | ||
($self: ident, $pending_msg_events: expr, $channel: expr, $channel_ready_msg: expr) => {{ | ||
if $channel.requires_manual_readiness_signal() { | ||
let mut pending_events = $self.pending_events.lock().unwrap(); | ||
pending_events.push(events::Event::PendingChannelReady { | ||
channel_id: $channel.channel_id(), | ||
user_channel_id: $channel.get_user_id(), | ||
counterparty_node_id: $channel.get_counterparty_node_id(), | ||
funding_outpoint: $channel.get_funding_txo().unwrap(), | ||
}); | ||
} else { | ||
send_channel_ready!($self, $pending_msg_events, $channel, $channel_ready_msg); | ||
} | ||
}} | ||
} | ||
|
||
macro_rules! send_channel_ready { | ||
($self: ident, $pending_msg_events: expr, $channel: expr, $channel_ready_msg: expr) => {{ | ||
$pending_msg_events.push(events::MessageSendEvent::SendChannelReady { | ||
|
@@ -4175,7 +4191,7 @@ where | |
} | ||
|
||
if let Some(msg) = channel_ready { | ||
send_channel_ready!(self, pending_msg_events, channel, msg); | ||
channel_ready_pending!(self, pending_msg_events, channel, msg); | ||
} | ||
if let Some(msg) = announcement_sigs { | ||
pending_msg_events.push(events::MessageSendEvent::SendAnnouncementSignatures { | ||
|
@@ -4354,6 +4370,38 @@ where | |
Ok(()) | ||
} | ||
|
||
/// Signals channel readiness after a [`Event::PendingChannelReady`]. | ||
/// | ||
/// The `channel_id` parameter indicates which channel should signal readiness, | ||
/// and the `counterparty_node_id` parameter is the id of the peer the channel is with. | ||
/// | ||
/// [`Event::PendingChannelReady`]: events::Event::PendingChannelReady | ||
pub fn signal_channel_readiness(&self, channel_id: &[u8; 32], counterparty_node_id: &PublicKey) -> Result<(), APIError> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's add a test for this. |
||
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier); | ||
|
||
let per_peer_state = self.per_peer_state.read().unwrap(); | ||
let peer_state_mutex = per_peer_state.get(counterparty_node_id) | ||
.ok_or_else(|| APIError::ChannelUnavailable { err: format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id) })?; | ||
let mut peer_state_lock = peer_state_mutex.lock().unwrap(); | ||
let peer_state = &mut *peer_state_lock; | ||
let pending_msg_events = &mut peer_state.pending_msg_events; | ||
match peer_state.channel_by_id.entry(channel_id.clone()) { | ||
hash_map::Entry::Occupied(mut channel) => { | ||
let best_block_height = self.best_block.read().unwrap().height(); | ||
let channel = channel.get_mut(); | ||
match channel.check_get_channel_ready(best_block_height) { | ||
Some(channel_ready) => send_channel_ready!(self, pending_msg_events, channel, channel_ready), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this just going to queue another There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah whoops, my bad. Makes sense to pull the conditional logic out of the macro and then the fn on ChannelManager the user uses can just call the macro to send it. |
||
None => return Err(APIError::APIMisuseError { err: "The channel isn't currently in a state where we can signal readiness.".to_owned() }) | ||
} | ||
} | ||
hash_map::Entry::Vacant(_) => { | ||
return Err(APIError::ChannelUnavailable { err: format!("Channel with id {} not found for the passed counterparty node_id {}", log_bytes!(*channel_id), counterparty_node_id) }); | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Gets the number of peers which match the given filter and do not have any funded, outbound, | ||
/// or 0-conf channels. | ||
/// | ||
|
@@ -5953,7 +6001,7 @@ where | |
HTLCDestination::NextHopChannel { node_id: Some(channel.get_counterparty_node_id()), channel_id: channel.channel_id() })); | ||
} | ||
if let Some(channel_ready) = channel_ready_opt { | ||
send_channel_ready!(self, pending_msg_events, channel, channel_ready); | ||
channel_ready_pending!(self, pending_msg_events, channel, channel_ready); | ||
if channel.is_usable() { | ||
log_trace!(self.logger, "Sending channel_ready with private initial channel_update for our counterparty on channel {}", log_bytes!(channel.channel_id())); | ||
if let Ok(msg) = self.get_channel_update_for_unicast(channel) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ | |
//! few other things. | ||
|
||
use crate::chain::keysinterface::SpendableOutputDescriptor; | ||
use crate::chain::transaction::OutPoint; | ||
#[cfg(anchors)] | ||
use crate::ln::chan_utils::{self, ChannelTransactionParameters, HTLCOutputInCommitment}; | ||
use crate::ln::channelmanager::{InterceptId, PaymentId}; | ||
|
@@ -817,6 +818,50 @@ pub enum Event { | |
/// transaction. | ||
claim_from_onchain_tx: bool, | ||
}, | ||
/// Indicates a channel has received sufficient confirmations and LDK is ready to send [`msgs::ChannelReady`]. | ||
/// | ||
/// To signal readiness, call [`ChannelManager::signal_channel_readiness`]. To reject the | ||
/// request, call [`ChannelManager::force_close_without_broadcasting_txn`]. | ||
/// | ||
/// The event is only triggered when the [`UserConfig::manually_signal_channel_ready`] | ||
/// config flag is set to true. | ||
/// | ||
/// [`ChannelManager::signal_channel_readiness`]: crate::ln::channelmanager::ChannelManager::signal_channel_readiness | ||
/// [`ChannelManager::force_close_without_broadcasting_txn`]: crate::ln::channelmanager::ChannelManager::force_close_without_broadcasting_txn | ||
/// [`UserConfig::manually_signal_channel_ready`]: crate::util::config::UserConfig::manually_signal_channel_ready | ||
/// [`msgs::ChannelReady`]: crate::ln::msgs::ChannelReady | ||
PendingChannelReady { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That sounds like the event itself marks the channel ready? I kinda like pending, tbh. |
||
/// The channel ID of the channel. | ||
/// | ||
/// When responding to the request, the `channel_id` should be passed | ||
/// back to the ChannelManager through [`ChannelManager::signal_channel_readiness`] to signal, | ||
/// or through [`ChannelManager::force_close_without_broadcasting_txn`] to reject. | ||
/// | ||
/// [`ChannelManager::signal_channel_readiness`]: crate::ln::channelmanager::ChannelManager::signal_channel_readiness | ||
/// [`ChannelManager::force_close_without_broadcasting_txn`]: crate::ln::channelmanager::ChannelManager::force_close_without_broadcasting_txn | ||
channel_id: [u8; 32], | ||
/// The `user_channel_id` value passed in to [`ChannelManager::create_channel`] for outbound | ||
/// channels, or to [`ChannelManager::accept_inbound_channel`] for inbound channels if | ||
/// [`UserConfig::manually_accept_inbound_channels`] config flag is set to true. Otherwise | ||
/// `user_channel_id` will be randomized for an inbound channel. | ||
/// | ||
/// [`ChannelManager::create_channel`]: crate::ln::channelmanager::ChannelManager::create_channel | ||
/// [`ChannelManager::accept_inbound_channel`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel | ||
/// [`UserConfig::manually_accept_inbound_channels`]: crate::util::config::UserConfig::manually_accept_inbound_channels | ||
user_channel_id: u128, | ||
/// The node_id of the counterparty requesting to open the channel. | ||
/// | ||
/// When responding to the request, the `counterparty_node_id` should be passed | ||
/// back to the `ChannelManager` through [`ChannelManager::signal_channel_readiness`] to | ||
/// signal readiness, or through [`ChannelManager::force_close_without_broadcasting_txn`] to reject the | ||
/// request. | ||
/// | ||
/// [`ChannelManager::signal_channel_readiness`]: crate::ln::channelmanager::ChannelManager::signal_channel_readiness | ||
/// [`ChannelManager::force_close_without_broadcasting_txn`]: crate::ln::channelmanager::ChannelManager::force_close_without_broadcasting_txn | ||
counterparty_node_id: PublicKey, | ||
/// The outpoint that holds the channel funds on-chain. | ||
funding_outpoint: OutPoint, | ||
}, | ||
/// Used to indicate that a channel with the given `channel_id` is ready to | ||
/// be used. This event is emitted either when the funding transaction has been confirmed | ||
/// on-chain, or, in case of a 0conf channel, when both parties have confirmed the channel | ||
|
@@ -1136,6 +1181,15 @@ impl Writeable for Event { | |
(6, channel_type, required), | ||
}); | ||
}, | ||
&Event::PendingChannelReady { ref channel_id, ref user_channel_id, ref counterparty_node_id, ref funding_outpoint } => { | ||
31u8.write(writer)?; | ||
write_tlv_fields!(writer, { | ||
(0, channel_id, required), | ||
(2, user_channel_id, required), | ||
(4, counterparty_node_id, required), | ||
(6, funding_outpoint, required) | ||
}); | ||
}, | ||
// Note that, going forward, all new events must only write data inside of | ||
// `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write | ||
// data via `write_tlv_fields`. | ||
|
@@ -1471,6 +1525,28 @@ impl MaybeReadable for Event { | |
}; | ||
f() | ||
}, | ||
31u8 => { | ||
let f = || { | ||
let mut channel_id = [0; 32]; | ||
let mut user_channel_id: u128 = 0; | ||
let mut counterparty_node_id = RequiredWrapper(None); | ||
let mut funding_outpoint = RequiredWrapper(None); | ||
read_tlv_fields!(reader, { | ||
(0, channel_id, required), | ||
(2, user_channel_id, required), | ||
(4, counterparty_node_id, required), | ||
(6, funding_outpoint, required), | ||
}); | ||
|
||
Ok(Some(Event::PendingChannelReady { | ||
channel_id, | ||
user_channel_id, | ||
counterparty_node_id: counterparty_node_id.0.unwrap(), | ||
funding_outpoint: funding_outpoint.0.unwrap() | ||
})) | ||
}; | ||
f() | ||
}, | ||
// Versions prior to 0.0.100 did not ignore odd types, instead returning InvalidValue. | ||
// Version 0.0.100 failed to properly ignore odd types, possibly resulting in corrupt | ||
// reads. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
send_channel_ready
macro doesn't seem like the proper place for this. At this point, we're ready to send outChannelReady
to our counterparty, and if we already received theirs, then we'll actually mark the channel as ready internally (seecheck_get_channel_ready
) and even receive aChannelReady
event immediately after. This may result in a HTLC being routed through the channel, even though we haven't signaled it ready ourselves. If we want to continue usingcheck_get_channel_ready
for the manual signal, we'll need to make sure it only returnsSome(ChannelReady)
oncesignal_channel_readiness
is called.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, I didn't look carefully enough at
check_get_channel_ready
and didn't realize it can update channel state.So the options are to update
check_get_channel_ready
so that like you said, it only returns the msg if manual is enabled and user has already signaled. This implies adding state to a channel to track whether or not user has signaled already.Is an alternative to update
check_get_channel_ready
so that it never returns Some(msg) if manual signaling is enabled and then write new logic in thesignal_channel_readiness
to make sure it's safe to send and then builds and sends the message?The latter approach would avoid adding additional state and is the way I'd lean going if you think it's okay?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This wouldn't allow the
ChannelReady
message to be retransmitted to the counterparty upon a reconnection andChannelReestablish
, unless anotherPendingChannelReady
event is queued for the consumer to signal. For your use case, it seems you only care about the firstChannelReady
that gets sent, excluding any further ones.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm true, I was thinking it would just continue to fire Pending as needed, requiring the user to continually perform whatever checks they need before sending.
I guess if we wanted to support only one approval from user then there's no way around adding state to the
Channel
so it knows whether or not it's received approval alreadyThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we can solve both problems at once and just add a
requires_manual_readiness_signal
toChannel
that gets set to the new config at creation and then when they manually signal we can flip it to false.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could, but I don't immediately see the use case for it. We already do something similar with our
ChannelReady
event – we only queue it once.Yeah that should work. Depending on how/when the event is queued, we may need another bit to make sure it's only queued once.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just realized we also potentially send ChannelReady as part of channel reestablishment and monitor update workflows. Seems like everywhere we might return Some(msg::ChannelReady) we would need to make sure it's been signaled already.
The issue then becomes where do we actually queue the PendingChannelReady event. I guess the normal path is part of
do_chain_event
workflow? Though maybe the first time can also be triggered after funding_created and a node restart via monitor update and 0-conf channel?I think I need some help figuring out best place to queue it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, I think we shouldn't concern ourselves with monitors or peer connectivity for generating the event itself - we should generate the event in the 0conf-signed/block-connection path (which all ends up in
check_get_channel_ready
I think). In those cases, we will want to not set theOurChannelReady
/move to theChannelReady
state until the user tells us we're ready to fly.We'll want to avoid sending the actual ready message if there's a monitor update pending or the peer is disconnected.
We'll also want to generate a fresh event on restart if we see that we're ready to fly (via
check_get_channel_ready
) but we're blocked on the user. That may be as simple as...doing nothing (but storing a "we generated the event" flag in memory only), as presumably there will (eventually) be another block connected on restart and we'll generate the event then.