-
Notifications
You must be signed in to change notification settings - Fork 407
Support manual signaling of channel readiness #2137
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
Support manual signaling of channel readiness #2137
Conversation
e55264c
to
c345478
Compare
@wpaulino would love another round of review on this approach when you get a chance |
Codecov ReportPatch coverage:
📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more Additional details and impacted files@@ Coverage Diff @@
## main #2137 +/- ##
==========================================
+ Coverage 91.40% 91.48% +0.07%
==========================================
Files 102 102
Lines 49563 50430 +867
Branches 49563 50430 +867
==========================================
+ Hits 45304 46136 +832
- Misses 4259 4294 +35
... and 5 files with indirect coverage changes Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. ☔ View full report in Codecov by Sentry. |
lightning/src/ln/channel.rs
Outdated
@@ -6484,6 +6500,7 @@ impl<Signer: WriteableEcdsaChannelSigner> Writeable for Channel<Signer> { | |||
(23, channel_ready_event_emitted, option), | |||
(25, user_id_high_opt, option), | |||
(27, self.channel_keys_id, required), | |||
(29, requires_manual_readiness_signal, option) |
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.
Hmm, maybe we should make this even with an option that's only Some
if the flag is set? If a user downgrades while the channel is still waiting for the remote's ChannelReady
, then they won't get the signal they explicitly requested.
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.
Interesting. So to make sure I understand. It's a user who had enabled this feature and got partially through the channel open process. They then downgrade LDK to a version before this feature exists. It means this TLV is persisted and the old version will just ignore it because it doesn't know about it.
In the downgraded ldk it will automatically signal channel ready without asking them.
I don't really think that's a problem? They explicitly downgraded to a version of LDK that no longer supports manual signaling?
If we make this even then it means when they try to start up the downgraded LDK it will panic when it tries to deserialize this type it doesn't know about?
Basically forcing them to upgrade or wait for a bugfix for whatever reason they downgraded in the first place?
If all of this is correct then I guess I'm not so sure what is the better experience. I guess it really just depends on why they want to manually signal and partially why they are downgrading.
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.
Generally, we don't support downgrading if you opt in to a new feature previously not supported, but will leave it up to @TheBlueMatt.
FWIW, for your use case of this feature, it seems like you wouldn't want a channel to enter ChannelReady
on its own if you downgrade.
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.
Agreed, though we currently are okay with it happening on the node today. But yeah, I guess in theory we would not want it to happen.
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 don't have a strong feeling here either. I assume there's no case where we could downgrade then forget to send the channelready, because we rebroadcast it on startup if we haven't exchanged any HTLCs...I'm fine with leaving it as-is.
let (channel_ready_opt, _) = channel.check_get_channel_ready(best_block_height); | ||
match channel_ready_opt { | ||
Some(channel_ready) => { | ||
channel.unset_requires_manual_readiness_signal(); |
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 doesn't seem to achieve anything? We decide not to further emit once we've done it 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.
I think we may even be able to just store one bool per channel, requires_manual_readiness_signal
, and flip it to false once we queue the PendingChannelReady
event.
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 yeah, that's how I originally had it but this comment #2109 (comment) made it seem like we wanted to re-generate the PendingChannelReady event on restart if they hadn't signaled yet.
So the flow is that a chain event happens, we generate PendingChannelReady, flip requires_manual_readiness_signal
to false, then LDK terminates.
On next startup, assuming the flag update was persisted, we will go ahead and automatically send ChannelReady when a block is connected without the user manually signaling it's okay.
So if we only have one flag and don't flip it until they signal then every time a block is found we would keep generating the event.
So one flag tracks whether or not we already emitted the event to prevent duplicates on subsequent chain events and the other is supposed to track whether this channel still requires manual signal from user.
I guess the way I have it currently will also persist the "we emitted" flag and would prevent a re-emission on startup which is why Matt mentioned in-memory only?
Thoughts? I'm kind of confusing myself again around this logic.
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.
Sorry for the confusion, the change is somewhat subtle but looks correct. I still don't think unset_requires_manual_readiness_signal
does anything though since we also check if we've already emitted an event in should_emit_pending_channel_ready_event
?
I guess the way I have it currently will also persist the "we emitted" flag and would prevent a re-emission on startup which is why Matt mentioned in-memory only?
I'm not seeing the immediate use case for regenerating it on startup if we need the user to signal. As long as we've already notified them once through the event (which is persisted), we just have to wait for them to handle it.
c345478
to
0137376
Compare
0137376
to
5c88c17
Compare
@@ -4968,12 +5036,12 @@ impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> { | |||
self.channel_update_status = status; | |||
} | |||
|
|||
fn check_get_channel_ready(&mut self, height: u32) -> Option<msgs::ChannelReady> { | |||
pub fn check_get_channel_ready(&mut self, height: u32) -> ChannelReadyStatus { |
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 needs to be pub(crate)
?
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) { |
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.
Let's first check that the channel must be in the appropriate state to do this (i.e., TheirChannelReady
or FundingSent
) and that it requires a manual signal before actually signaling.
@@ -75,6 +97,24 @@ pub struct AvailableBalances { | |||
pub next_outbound_htlc_limit_msat: u64, | |||
} | |||
|
|||
pub(crate) struct ChainActionUpdates { | |||
pub(crate) channel_ready_msg: Option<msgs::ChannelReady>, |
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.
Is it any nicer if we reuse the ChannelReadyStatus
enum here as well?
lightning/src/ln/channel.rs
Outdated
@@ -6484,6 +6500,7 @@ impl<Signer: WriteableEcdsaChannelSigner> Writeable for Channel<Signer> { | |||
(23, channel_ready_event_emitted, option), | |||
(25, user_id_high_opt, option), | |||
(27, self.channel_keys_id, required), | |||
(29, requires_manual_readiness_signal, option) |
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.
Generally, we don't support downgrading if you opt in to a new feature previously not supported, but will leave it up to @TheBlueMatt.
FWIW, for your use case of this feature, it seems like you wouldn't want a channel to enter ChannelReady
on its own if you downgrade.
let (channel_ready_opt, _) = channel.check_get_channel_ready(best_block_height); | ||
match channel_ready_opt { | ||
Some(channel_ready) => { | ||
channel.unset_requires_manual_readiness_signal(); |
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.
Sorry for the confusion, the change is somewhat subtle but looks correct. I still don't think unset_requires_manual_readiness_signal
does anything though since we also check if we've already emitted an event in should_emit_pending_channel_ready_event
?
I guess the way I have it currently will also persist the "we emitted" flag and would prevent a re-emission on startup which is why Matt mentioned in-memory only?
I'm not seeing the immediate use case for regenerating it on startup if we need the user to signal. As long as we've already notified them once through the event (which is persisted), we just have to wait for them to handle it.
We should definitely have test coverage for this on both zero conf and conf channels. |
/// 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Would this API be easier to use if we allow it to be called before the PendingChannelReady
event? Wouldn't be much code difference, but would let users also use this as a method to "turn on" a channel which hasn't yet fully confirmed, as long as their peer does as well. You could even opt into 0conf after-the-fact with it (which I think is how CLN does 0conf).
We could also do that later but in the mean time at least allow this method to be called and unset the wait-for-event bit on the channel even if we're not ready to send the channel_ready.
@@ -1504,6 +1504,23 @@ macro_rules! emit_channel_ready_event { | |||
} | |||
} | |||
|
|||
macro_rules! emit_pending_channel_ready_event { |
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 macro is only used once? Can we inline the code?
}); | ||
}, | ||
ChannelReadyStatus::PendingChannelReady => { | ||
return Ok(ChainActionUpdates { |
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.
Won't we generate a new event on every block? We should probably just do it once.
Actually, does it make sense to drop the event entirely from this PR and rely on #2098 ? |
Great idea, would simplify this PR a good bit. |
Yea, we will need to port the logic around "if we restart and a channel is (a) set to manual-ready and (b) not yet set to allow-ready regenerate the event" to the event from that PR, but that seems fine? |
Why would we need to regenerate it? The user should manually signal upon processing the event the first time. |
Hmmmm, I think there's a case where we (a) broadcast funding, (b) the funding confirmes, (c) we restart without persisting the channelmanager. On restart I think the event will be gone and we won't get it back - #2098 (comment) |
Interesting, I'd assume the channel would get closed immediately upon restarting. Do we have any assurances that the channel will be in a functional state after the restart? The blocks would be replayed and the funding confirmation would be picked up, but what if we're using async monitor persistence? Would a |
I don't see why? We're past funding signed exchange, so its "locked in", and hopefully we don't have an HTLC exchange before we shut down.
Heh, I wasn't referring to anything this subtle lol. In #2098 the event is generated literally by the act of broadcasting the funding tx, if we don't have to broadcast the funding tx on restart because its been confirmed we wont regenerate the event, even though if this PR is based on it we'll be stuck waiting for the event to ready the channel. |
Ok so is the latest thinking to:
i guess for inbound channels (what we're concerned with here) we wont necessarily have the funding tx in our bitcoind when we receive ChannelPending where as with this PendingChannelReady we will have received confirmations already assuming we aren't using 0-conf inbound (which we wont). it means our workflow would have to be something like maintain a queue of channel_ids / funding_txo that we push to when we receive a Or is there a simpler workflow we can use? or am I misunderstanding the suggestion? |
I think so.
I think this is going to be a large overhaul and require a few new tests. I'm fine if you dont want to do this now, but I think it should be done long-term.
Yea.
Yea, that's correct. If that's substantial additional complexity on your end we can do the event still or make the event appear on 1-conf irrespective of the channel config. We could also just make the event repeat, but I think that's a bit weird. |
Ok, that's not too bad. It's much better than one option I had floated to avoid changing ldk all which was to basically filter and screen every single tx before we feed it to ldk. This allows us to only screen relevant txs. Actually, couldn't we basically do this without any of these changes by just querying our channels on each block connected to see if there are any channels that are (funding_txo.is_some() && !is_outbound && confirmations < confirmations_required). for each channel in that state we can check if the funding tx is in the block, if it is, screen the inputs and close the channel id needed. as long as we don't accept 0 (or 1) conf inbound channels this should be fine? |
Right, as long as you dont accept 0/1 confs that would totally work. |
Can this be closed now @johncantrell97? I think 1 conf should also work since you're checking transactions before providing them to LDK. |
Ah, well i'm not actually doing it before providing to LDK. I guess I could change that but currently it's just adding a third I guess maybe it's better to have a single Listener that can pipe them wherever/whenever it needs. As for this PR, I think it's probably not so important for us anymore. Though having manual signaling would be much more explicit then this new approach. Do you think it's worth implementing this feature without the event? |
I think manual signaling is kinda useful/nice to have, its not all that critical but no reason not to have it. Further having the ability to make a channel ready before confirmation is quite nice to have, and worth doing, but also more complicated. |
Any desire to keep working on this (no pressure, we can also close)? |
@johncantrell97 closing for now, feel free to re-open if this would still be useful to you. |
@johncantrell97 Looks like this may be useful for a 0-conf channel open flow from a swap-in-potentiam address, what can be done to revive this PR? |
In addition, I need to get the |
Second pass at implementing #2106
Refactors the unnamed tuple that gets returned as part of
do_chain_work
flow with aChainActionUpdates
struct.Adds a manually_signal_channel_ready config option to
ChannelHandshakeConfig
for users to configure a channel with this option. Defaults to false.Saves this option to Channel using
requires_manual_readiness_signal
and this same flag is updated when user manually signals readiness.Tracks emission of
PendingChannelReady
event using new flag onChannel
pending_channel_ready_event_emitted
similar to how theChannelReady
event works.Adds a new
ChainActionUpdates
field to signal to ChannelManager when it's time to generate thePendingChannelReady
event.