-
Notifications
You must be signed in to change notification settings - Fork 404
Exchange splice_locked
messages
#3741
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
base: main
Are you sure you want to change the base?
Conversation
👋 Thanks for assigning @TheBlueMatt as a reviewer! |
The logic around determining if the |
When processing confirmed transactions and updates to the best block, ChannelManager may be instructed to send a channel_ready message when a channel's funding transaction has confirmed and has met the required number of confirmations. A similar action is needed for sending splice_locked once splice transaction has confirmed with required number of confirmations. Generalize do_chain_event signature to allow for either scenario.
When processing confirmed transactions, if the funding transaction is found then information about it in the ChannelContext is updated. In preparation for splicing, move this data to FundingScope.
dfbc04e
to
e4c0566
Compare
@wpaulino Ok, this is in better shape for a high-level look. I don't believe it correctly handles unconfirmed splice transactions yet. Also, doesn't yet re-send |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3741 +/- ##
==========================================
+ Coverage 89.10% 90.34% +1.24%
==========================================
Files 156 158 +2
Lines 123431 135603 +12172
Branches 123431 135603 +12172
==========================================
+ Hits 109985 122515 +12530
+ Misses 10760 10568 -192
+ Partials 2686 2520 -166 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
🔔 1st Reminder Hey @wpaulino! This PR has been waiting for your review. |
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.
LGTM. The preparational changes are very clear. The WIP part also makes sense so far.
@@ -11725,6 +11727,13 @@ where | |||
log_trace!(logger, "Sending channel_ready WITHOUT channel_update for {}", funded_channel.context.channel_id()); | |||
} | |||
}, | |||
#[cfg(splicing)] | |||
Some(FundingConfirmedMessage::Splice(splice_locked)) => { | |||
pending_msg_events.push(MessageSendEvent::SendSpliceLocked { |
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.
TODO note: An event will have to be sent here as well, similar to ChannelReady
-- ChannelReady
with a flag indicating it is for splicing, or a new, splicing-specific 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.
I take it we want this only after both splice_locked
messages have been exchanged? For naming, debating either SpliceLocked
or SpliceReady
. Something like SpliceFunded
also a possibility though it seems odd if it is a splice-out.
Related: Do we want something similar to ChannelPending
for when tx_signatures
have been exchanged?
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.
Do we want something similar to ChannelPending for when tx_signatures have been exchanged?
I think so, that way we can let the user know their splice was negotiated successfully and is pending confirmation.
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.
Added a SpliceLocked
event but open to renaming it. I'm a bit uncertain on ordering wrt emitting this event and sending splice_locked
, if it matters. Open question as to what should go into the event, presumably from negotiation.
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.
Shouldn't it just mimic channel_ready
?
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.
Discussed offline. emit_channel_ready_event
has some additional checks that aren't needed here. Instead, we simply emit the event when we have Some
announcement signatures.
Also, I ended up dropping the event in favor of re-using ChannelReady
and added the funding_txo
to the event. This can be used to determine if it corresponds to a ChannelPending
or an upcoming SplicePending
event.
@@ -1864,6 +1864,8 @@ impl FundingScope { | |||
#[cfg(splicing)] | |||
struct PendingSplice { | |||
pub our_funding_contribution: i64, | |||
sent_funding_txid: Option<Txid>, |
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.
Where is this field set? Is it when signatures have been exchanged for the the negotiated funding transaction / it has been broadcast?
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 believe these are specific to splice_locked
, so when that is sent/received.
🔔 2nd Reminder Hey @wpaulino! This PR has been waiting for your review. |
lightning/src/ln/channel.rs
Outdated
self.context.minimum_depth = Some(COINBASE_MATURITY); | ||
self.funding.minimum_depth.unwrap_or(0) > 0 && | ||
self.funding.minimum_depth.unwrap_or(0) < COINBASE_MATURITY { | ||
self.funding.minimum_depth = Some(COINBASE_MATURITY); |
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.
Can we somehow enforce the coinbase maturity without updating minimum_depth
? We always keep the funding transaction around now so we should be able to check if it's a coinbase transaction or not. That would prevent us from tracking minimum_depth
per scope.
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.
Looks like for v1 channel establishment, the acceptor doesn't have the funding transaction until they see the funding txo on chain. So we need to set it then. We could only store it if it's the coinbase transaction, but I've set it unconditionally for now.
@@ -1864,6 +1864,8 @@ impl FundingScope { | |||
#[cfg(splicing)] | |||
struct PendingSplice { | |||
pub our_funding_contribution: i64, | |||
sent_funding_txid: Option<Txid>, |
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 believe these are specific to splice_locked
, so when that is sent/received.
|
||
match pending_splice.sent_funding_txid { | ||
Some(sent_funding_txid) if confirmed_funding_txid == sent_funding_txid => { | ||
debug_assert!(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 may need to replay splice_locked
if a disconnect happened before they were able to process it. Unclear if we'd want to use this same code path for it or not.
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.
By this do you mean for channel_reestablish
?
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.
Yeah, I assume there is a similar retransmission case we need to handle there, like with channel_ready
.
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.
Gonna leave this for a follow-up, if that's ok.
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.
Sure, probably better to wait for #3637 to land anyway.
lightning/src/ln/channel.rs
Outdated
}; | ||
|
||
let mut confirmed_funding_tx = None; | ||
for &(index_in_block, tx) in txdata.iter() { |
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 want to iterate over the funding scopes for each transaction instead, since we may be processing full block data with thousands of transactions and having to compute the txid
of each one.
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.
Added some fixups for this as discussed offline.
e4c0566
to
49f8ef6
Compare
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.
Addressed most comments other than adding an event and for re-sending splice_locked
. See comment replies for open questions.
Also, added code to insert the new scid in short_to_chan_info
. Should we remove the old one?
lightning/src/ln/channel.rs
Outdated
self.context.minimum_depth = Some(COINBASE_MATURITY); | ||
self.funding.minimum_depth.unwrap_or(0) > 0 && | ||
self.funding.minimum_depth.unwrap_or(0) < COINBASE_MATURITY { | ||
self.funding.minimum_depth = Some(COINBASE_MATURITY); |
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.
Looks like for v1 channel establishment, the acceptor doesn't have the funding transaction until they see the funding txo on chain. So we need to set it then. We could only store it if it's the coinbase transaction, but I've set it unconditionally for now.
lightning/src/ln/channel.rs
Outdated
}; | ||
|
||
let mut confirmed_funding_tx = None; | ||
for &(index_in_block, tx) in txdata.iter() { |
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.
Added some fixups for this as discussed offline.
|
||
match pending_splice.sent_funding_txid { | ||
Some(sent_funding_txid) if confirmed_funding_txid == sent_funding_txid => { | ||
debug_assert!(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.
By this do you mean for channel_reestablish
?
@@ -11725,6 +11727,13 @@ where | |||
log_trace!(logger, "Sending channel_ready WITHOUT channel_update for {}", funded_channel.context.channel_id()); | |||
} | |||
}, | |||
#[cfg(splicing)] | |||
Some(FundingConfirmedMessage::Splice(splice_locked)) => { | |||
pending_msg_events.push(MessageSendEvent::SendSpliceLocked { |
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 take it we want this only after both splice_locked
messages have been exchanged? For naming, debating either SpliceLocked
or SpliceReady
. Something like SpliceFunded
also a possibility though it seems odd if it is a splice-out.
Related: Do we want something similar to ChannelPending
for when tx_signatures
have been exchanged?
ef048e6
to
f3b0989
Compare
Squashed fixups |
An offline discussion we had around |
@@ -3026,7 +3026,7 @@ macro_rules! locked_close_channel { | |||
// into the map (which prevents the `PeerState` from being cleaned up) for channels that | |||
// never even got confirmations (which would open us up to DoS attacks). | |||
let update_id = $channel_context.get_latest_monitor_update_id(); | |||
if $channel_context.get_funding_tx_confirmation_height().is_some() || $channel_context.minimum_depth() == Some(0) || update_id > 1 { | |||
if $channel_funding.get_funding_tx_confirmation_height().is_some() || $channel_context.minimum_depth() == Some(0) || update_id > 1 { |
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 thought we agreed to try to keep the concept of funding scopes outside of ChannelManager
? I think for this we can move to a is_funding_confirmed_or_0conf
call or something?
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 doesn't have access to the channel, only the context which doesn't have reference to the funding scope. So it would require much more re-work than I'd like to do in this PR.
Instead, I'd rather make a separate PR after all the changes needed for FundingScope
are complete. Hopefully in a couple PRs.
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.
Oh, also forgot to drop these two commits since one reverts the previous one. But the general idea still stands for other occurrences.
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.
Tried to avoid this but the problem is that locked_close_channel
is called throughout the file including by convert_channel_err
, which likewise is called throughout. Additionally, convert_channel_err
has a rule that expects a FundedChannel
so that it can pass it to get_channel_update_for_broadcast
.
Might be a way to refactor this but I think it should wait for a follow-up given it's not going to be straightforward.
lightning/src/ln/channelmanager.rs
Outdated
@@ -11639,7 +11639,7 @@ where | |||
for chan in peer_state.channel_by_id.values().filter_map(Channel::as_funded) { | |||
let txid_opt = chan.funding.get_funding_txo(); | |||
let height_opt = chan.context.get_funding_tx_confirmation_height(); | |||
let hash_opt = chan.context.get_funding_tx_confirmed_in(); | |||
let hash_opt = chan.funding.get_funding_tx_confirmed_in(); |
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.
Can we avoid new references to Channel::funding
in ChannelManager
? I thought we wanted to remove that?
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 was able to get rid of this one by moving get_funding_tx_confirmed_in
to FundedChannel
since this is the only use.
@@ -3031,7 +3031,7 @@ macro_rules! locked_close_channel { | |||
$peer_state.closed_channel_monitor_update_ids.insert(chan_id, update_id); | |||
} | |||
let mut short_to_chan_info = $self.short_to_chan_info.write().unwrap(); | |||
if let Some(short_id) = $channel_context.get_short_channel_id() { | |||
if let Some(short_id) = $channel_funding.get_short_channel_id() { |
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.
Same here, can we avoid this change?
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.
Similarly, we'd need this to take a Channel
instead of a ChannelContext
, but the problem is that convert_channel_err
needs a FundedChannel
for one rule as mentioned in another comment. Maybe we eventually just add any methods called from those macros on both Channel
and each sub-struct so that it can be used either way?
Separate but related to this, I'm wondering now which Currently, we use the pre-splice
After that So in the scenario described above, should we transition to the pending Then that way for Related discussion: #3592 (comment) |
When processing confirmed transactions, if the funding transaction is found then information about it in the ChannelContext is updated. In preparation for splicing, move this data to FundingScope.
When processing confirmed transactions, if the funding transaction is found then information about it in the ChannelContext is updated. In preparation for splicing, move this data to FundingScope.
When checking if channel_ready should be sent, the funding transaction must reach minimum_depth confirmations. The same logic is needed for splicing a channel, so refactor it into a helper method.
The minimum_depth of a channel is overridden to COINBASE_MATURITY if the funding transaction is the coinbase transaction. However, if this is to be reused for a splice's minimum_depth, it would be a problem since sending splice_locked would be unnecessarily delayed. Now that FundingScope contains the funding transaction, use this to check if it is a coinbase transaction instead of overriding minimum_depth.
f3b0989
to
bd1a788
Compare
Discussed offline with @TheBlueMatt and @wpaulino. The two scenarios are closing while funding negotiation takes place and while waiting for the splice to confirm. For the latter, it would be better to a have general approach in For the former, we won't have a |
When transactions confirm or the best block is updated, check if any pending splice funding transactions have confirmed to an acceptable depth. If so, send a splice_locked message to the counterparty and -- if the counterparty has exchanged a splice_locked message for the same funding txid -- promote the corresponding FundingScope such that the new funding can be utilized.
b4e2b1a
to
a57c034
Compare
Pushed some fixups to hopefully fix CI. Also, updated the WIP commit message. |
One more fixup... |
🔔 1st Reminder Hey @TheBlueMatt @dunxen @wpaulino! This PR has been waiting for your review. |
2 similar comments
🔔 1st Reminder Hey @TheBlueMatt @dunxen @wpaulino! This PR has been waiting for your review. |
🔔 1st Reminder Hey @TheBlueMatt @dunxen @wpaulino! This PR has been waiting for your review. |
🔔 2nd Reminder Hey @TheBlueMatt @dunxen @wpaulino! This PR has been waiting for your review. |
2 similar comments
🔔 2nd Reminder Hey @TheBlueMatt @dunxen @wpaulino! This PR has been waiting for your review. |
🔔 2nd Reminder Hey @TheBlueMatt @dunxen @wpaulino! This PR has been waiting for your review. |
🔔 3rd Reminder Hey @TheBlueMatt @dunxen @wpaulino! This PR has been waiting for your review. |
@@ -4851,7 +4851,24 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider { | |||
} | |||
|
|||
fn check_funding_confirmations(&self, funding: &mut FundingScope, height: u32) -> bool { | |||
if funding.funding_tx_confirmation_height == 0 && self.minimum_depth != Some(0) { | |||
let is_coinbase = funding |
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.
Isn't this unreachable for splicing anyway as is_coinbase()
checks that the transaction has a single input with a txid of all-0s but splicing checks the transaction -> txid conversion and requires an input that is the previous funding?
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.
Yeah, that is correct.
.map(|tx| tx.is_coinbase()) | ||
.unwrap_or(false); | ||
|
||
let minimum_depth = { |
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.
Using this directly rather than actually setting self.minimum_depth
for coinbase-tx-funded channels changes the public API to no longer display 100 for the minimum depth in ChannelDetails
. I don't really feel super strongly against this, but it does seem like changing to 100 makes sense given that is actually our minimum depth?
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.
Good catch. I suppose we could do something similar when constructing ChannelDetails
.
}; | ||
|
||
match pending_splice.sent_funding_txid { | ||
Some(sent_funding_txid) if confirmed_funding_txid == sent_funding_txid => { |
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'm really confused what we're doing here, honestly. We have both a sent
and received
funding_txid
, but presumably those should be identical after we finish negotiation, so why are we storing separate txids? Then here we're calling the FundingScope
txid confirmed, but why? Shouldn't that be filled in when the FundingScope
is built (after splicing negotiation completes, I assume? Finally, we send a SpliceLocked
if either sent_funding_txid
is None
(makes sense) or if the FundingScope
funding info doesn't match the send_funding_txid
, but why can the funding txid change at all? Shouldn't it be fixed after splice negotiation finishes? Is this some kind of RBF thing?
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.
Yeah, it is to do with RBF. sent_funding_txid
and received_funding_txid
correspond to the funding_txid
used when sending or receiving a splice_locked
, respectively. They are only set then, not after negotiation.
Here, we may have sent a splice_locked
for one splice transaction but later an RBF confirms. Though, now I'm wondering if I've handled the case of unconfirming the first splice tx, which I'd imagine should clear sent_funding_txid
?
|
||
#[cfg(splicing)] | ||
fn check_get_splice_locked( | ||
&mut self, pending_splice: &PendingSplice, funding: &mut FundingScope, height: u32, |
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 fact that we're passing in a PendingSplice
and FundingScope
separately tells me our datastructures are wrong. FundedChannel
holds a FundingScope
for the confirmed funding and a PendingSplice
(I assume) for the in-flight negotiation. Once negotiation finishes, its a bit weird that we then have a PendingSplice
in the FundedChannel
that refers to a FundingScope
that is actually in FundedChannel::pending_funding
by this point.
PendingSplice
's docs say that it is "Info about a pending splice, used in the pre-splice channel", which implies to me that it should go away once we've negotiated a splice and we're ready to lock it in. Shouldn't we track it in the FundingScope
that it refers to once one exists? Given that we can have multiple RBFs its a bit weird that we track our_funding_contribution
singularly globally but can have many values across different RBFs.
I guess the intent here is to track "what txid of all the RBFs did they tell us is confirmed" separately from those RBFs so we can detect inconsistency between what we saw confirmed and what our peer saw confirmed, tho it might be worth noting that we actually can happily continue if we're split-brained with our peer - as long as we keep getting sigs for what we think is one chain its fine...Awkward, but not worth having an awkward design to detect.
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 fact that we're passing in a
PendingSplice
andFundingScope
separately tells me our datastructures are wrong.FundedChannel
holds aFundingScope
for the confirmed funding and aPendingSplice
(I assume) for the in-flight negotiation. Once negotiation finishes, its a bit weird that we then have aPendingSplice
in theFundedChannel
that refers to aFundingScope
that is actually inFundedChannel::pending_funding
by this point.
Hmm... PendingSplice
doesn't refer to those. If you mean sent_funding_txid
and received_funding_txid
, those are only set when splice_locekd
is sent or received, respectively, and could correspond to any FundingScope
in FundedChannel::pending_funding
. In fact, we may want to move FundedChannel::pending_funding
into PendingSplice
as mentioned in a TODO. Though maybe not? See below.
PendingSplice
's docs say that it is "Info about a pending splice, used in the pre-splice channel", which implies to me that it should go away once we've negotiated a splice and we're ready to lock it in. Shouldn't we track it in theFundingScope
that it refers to once one exists? Given that we can have multiple RBFs its a bit weird that we trackour_funding_contribution
singularly globally but can have many values across different RBFs.
Note that we are adding more fields in #3736.
@wpaulino Could you recall why we want to re-use PendingSplice
for both current negotiation context and post-negotiation state? Was it simply to avoid adding another field / struct to FundedChannel
? Perhaps it would be better to have separate structs as @TheBlueMatt points out.
I guess the intent here is to track "what txid of all the RBFs did they tell us is confirmed" separately from those RBFs so we can detect inconsistency between what we saw confirmed and what our peer saw confirmed, tho it might be worth noting that we actually can happily continue if we're split-brained with our peer - as long as we keep getting sigs for what we think is one chain its fine...Awkward, but not worth having an awkward design to detect.
Right, I think we do happily continue in this case. If sent_funding_txid
and received_funding_txid
differ, we never call promote_splice_funding
or send announcement sigs.
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.
Could you recall why we want to re-use PendingSplice for both current negotiation context and post-negotiation state? Was it simply to avoid adding another field / struct to FundedChannel? Perhaps it would be better to have separate structs as @TheBlueMatt points out.
PendingSplice
and pending_funding
have mostly been worked on independently, and it wasn't until #3736 that it become more obvious that they should be unified as we discussed in our last sync. pending_funding
should definitely be tracked within PendingSplice
now, there's no way those can be set without PendingSplice
also being set, assuming PendingSplice
is Some
until both splice_locked
s have been exchanged.
As for some of the negotiation state (e.g., our_funding_contribution
, etc.), I'd prefer having them captured within PendingSplice
so that we can always rely on pending_splice
being None
before attempting a splice. Ideally all that state is captured under an optional member we can set to None
once the negotiation is finished and we're waiting confirmations. That state may go back to being Some
(temporarily) if a RBF attempt is made though.
As an aside, it seems like this method (check_get_splice_locked
) doesn't belong on the ChannelContext
, it should be on FundedChannel
instead.
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.
PendingSplice
andpending_funding
have mostly been worked on independently, and it wasn't until #3736 that it become more obvious that they should be unified as we discussed in our last sync.pending_funding
should definitely be tracked withinPendingSplice
now, there's no way those can be set withoutPendingSplice
also being set, assumingPendingSplice
isSome
until bothsplice_locked
s have been exchanged.
Yeah, let me give that a try as discussed last time.
As for some of the negotiation state (e.g.,
our_funding_contribution
, etc.), I'd prefer having them captured withinPendingSplice
so that we can always rely onpending_splice
beingNone
before attempting a splice. Ideally all that state is captured under an optional member we can set toNone
once the negotiation is finished and we're waiting confirmations. That state may go back to beingSome
(temporarily) if a RBF attempt is made though.
Ah, right, starting to recall our offline discussion of this.
As an aside, it seems like this method (
check_get_splice_locked
) doesn't belong on theChannelContext
, it should be onFundedChannel
instead.
Currently, the borrow checker complains because the FundingScope
mutable reference is passed along to check_funding_confirmations
, but the self
parameter is a also a mutable reference. The latter can be made immutable but that is still a problem with the borrow checker.
IIUC, a mutable reference is need because FundingScope::funding_tx_confirmation_height
is set to zero when given a height that would make the local variable funding_tx_confirmations
negative. I don't know if doing this is strictly necessary (looking now). The side effect is unfortunate, though.
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.
IIUC, a mutable reference is need because FundingScope::funding_tx_confirmation_height is set to zero when given a height that would make the local variable funding_tx_confirmations negative. I don't know if doing this is strictly necessary (looking now). The side effect is unfortunate, though.
Ah, IMO that should be reset right as we see the height the transaction confirmed in being disconnected, not sure how much work that refactor would be though. As I was going through this logic, I also noticed we need to update ChannelManager::transaction_confirmed
to consider all scopes to handle splice transactions.
None => { | ||
// TODO: Move pending_funding into pending_splice? | ||
debug_assert!(false); | ||
// TODO: Error instead? |
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.
Yes please :)
} | ||
} | ||
|
||
self.context.check_for_funding_tx_spent(&self.funding, tx, logger)?; |
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 checking against self.funding
seems insufficient, no? Can't the peer see the new splice lock-in, but then never send a splice_locked
and then FC the channel against the splice in the next block, which wouldn't result in us not checking if that funding is spent?
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.
Yeah, I think you're right. We could see the splice confirm in the previous block without enough confirmations, regardless, before the force close in the next block.
let mut confirmed_funding = None; | ||
#[cfg(splicing)] | ||
for funding in self.pending_funding.iter_mut() { | ||
if confirmed_funding.is_some() { |
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.
Huh? This is set unconditionally, so won't this fail any time we have more than one pending_funding
/any RBF?
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... yeah something is screwy here. There's a similar check in transactions_confirmed
to make sure we never have more than one pending splice confirm in the same block, which shouldn't be possible. Here, we really want to use whichever one has confirmed, if any. I suppose we should check FundingScope::funding_tx_confirmed_in
.
}} | ||
} | ||
|
||
macro_rules! insert_short_channel_id { |
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, ugh, I guess we need to store all past (recent-ish?) SCIDs for a channel? If we restart we still presumably need to keep relaying using past SCIDs post-splice-lock-in for an hour or three worth of blocks.
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... is it worth cleaning these up after some time?
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, ugh, I guess we need to store all past (recent-ish?) SCIDs for a channel?
Yeah this is a bit unfortunate. Easiest way to clean them up as we go is probably on deserialization by checking if any pending HTLCs are still referencing an old SCID.
return Ok(self.get_announcement_sigs(node_signer, chain_hash, user_config, best_block.height, logger)); | ||
} | ||
|
||
// TODO: Close channel? |
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 not leave question TODOs behind. We should decide if we want to or not and do it (or not). I don't feel strongly here, but a TODO is the wrong answer.
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.
Right, this was really meant to resolve during the review. Should have left a comment.
Here we have sent a splice_locked
for pending_splice.sent_funding_txid
but we can't fine a corresponding FundingScope
when our counterparty sends their splice_locked
. This is a bad state. From our counterparty's perspective, the splice is now locked. So presumably future operations would fail. So we might as well close the channel now.
Once both parties have exchanged splice_locked messages, the splice funding is ready for use. Emit an event to the user indicating as much.
A ChannelReady event is used for both channel establishment and splicing to indicate that the funding transaction is confirmed to an acceptable depth and thus the channel can be used with the funding. An upcoming SplicePending event will be emitted for each pending splice (i.e., both the initial splice attempt and any RBF attempts). Thus, when a ChannelReady event is emitted, the funding_txo must be included to differentiate between which ChannelPending -- which also contains the funding_txo -- that the event corresponds to.
bbf73c7
to
81c410d
Compare
lightning/src/ln/channelmanager.rs
Outdated
@@ -11639,7 +11639,7 @@ where | |||
for chan in peer_state.channel_by_id.values().filter_map(Channel::as_funded) { | |||
let txid_opt = chan.funding.get_funding_txo(); | |||
let height_opt = chan.context.get_funding_tx_confirmation_height(); | |||
let hash_opt = chan.context.get_funding_tx_confirmed_in(); | |||
let hash_opt = chan.funding.get_funding_tx_confirmed_in(); |
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.
get_relevant_txids
will need to look at any pending scopes for the new funding txids so that those can be notified by the backend (bitcoind, electrum, etc.) as well.
|
||
#[cfg(splicing)] | ||
fn check_get_splice_locked( | ||
&mut self, pending_splice: &PendingSplice, funding: &mut FundingScope, height: u32, |
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.
IIUC, a mutable reference is need because FundingScope::funding_tx_confirmation_height is set to zero when given a height that would make the local variable funding_tx_confirmations negative. I don't know if doing this is strictly necessary (looking now). The side effect is unfortunate, though.
Ah, IMO that should be reset right as we see the height the transaction confirmed in being disconnected, not sure how much work that refactor would be though. As I was going through this logic, I also noticed we need to update ChannelManager::transaction_confirmed
to consider all scopes to handle splice transactions.
if let Some(channel_ready) = self.check_get_channel_ready(height, logger) { | ||
log_info!(logger, "Sending a channel_ready to our peer for channel {}", &self.context.channel_id); | ||
let announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger); | ||
return Ok((Some(FundingConfirmedMessage::Establishment(channel_ready)), announcement_sigs)); |
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 don't think returning early is ok here, don't we still need to check if it was spent?
lightning/src/ln/channel.rs
Outdated
for &(_, tx) in txdata.iter() { | ||
self.context.check_for_funding_tx_spent(&self.funding, tx, logger)?; |
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.
Ideally we also resume scanning from the index that we stopped at above, not a huge deal though.
return Ok((Some(FundingConfirmedMessage::Splice(splice_locked)), announcement_sigs)); | ||
} | ||
|
||
return Ok((Some(FundingConfirmedMessage::Splice(splice_locked)), None)); |
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.
Same comment here about returning early.
}} | ||
} | ||
|
||
macro_rules! insert_short_channel_id { |
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, ugh, I guess we need to store all past (recent-ish?) SCIDs for a channel?
Yeah this is a bit unfortunate. Easiest way to clean them up as we go is probably on deserialization by checking if any pending HTLCs are still referencing an old SCID.
let pending_splice = match self.pending_splice.as_mut() { | ||
Some(pending_splice) => pending_splice, | ||
None => { | ||
return Err(ChannelError::Warn(format!("Channel is not in pending splice"))); |
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.
Maybe ignore instead? We can receive it again upon reconnecting even if we already transitioned to the new scope.
let persist = match &res { | ||
Err(e) if e.closes_channel() => NotifyOption::DoPersist, | ||
Err(_) => NotifyOption::SkipPersistHandleEvents, | ||
Ok(()) => NotifyOption::SkipPersistHandleEvents, |
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 do want to persist here if either we promoted the splice funding, or we received their splice_locked
but we haven't detected the confirmation yet.
After a splice has been negotiated, each party must send a
splice_locked
message to the other party once the splice transaction has had an acceptable number of confirmations. Update the logic for processing newly confirmed transactions and updated best block to sendsplice_locked
when appropriate.Likewise, handle
splice_locked
and promote the channel'sFundingScope
once bothsplice_locked
messages have been exchanged.