-
Notifications
You must be signed in to change notification settings - Fork 417
Splice channel state rework #4061
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?
Splice channel state rework #4061
Conversation
👋 Thanks for assigning @jkczyz as a reviewer! |
lightning/src/ln/channel.rs
Outdated
// FIXME: Either we bring back the `OUR_TX_SIGNATURES_READY` flag for this specific | ||
// case, or we somehow access the signing session to check it instead. | ||
ChannelState::FundingNegotiated(flags) => !flags.is_interactive_signing(), |
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 current implementation has a potential logic issue. The FIXME comment correctly identifies that is_interactive_signing()
is being used as a replacement for the removed OUR_TX_SIGNATURES_READY
flag, but these represent different states:
is_interactive_signing()
is true during the entire interactive signing processOUR_TX_SIGNATURES_READY
was specifically true only after our signatures were ready to send
This semantic difference could cause funding transactions to be incorrectly marked as non-broadcastable when they should be broadcastable. Consider either:
- Restoring the
OUR_TX_SIGNATURES_READY
flag for this specific use case, or - Accessing the signing session directly to check if holder signatures are ready via something like
signing_session.holder_tx_signatures().is_some()
This would more accurately represent the original intent of the code.
// FIXME: Either we bring back the `OUR_TX_SIGNATURES_READY` flag for this specific | |
// case, or we somehow access the signing session to check it instead. | |
ChannelState::FundingNegotiated(flags) => !flags.is_interactive_signing(), | |
// We need to check if our signatures are ready to send, which means the transaction | |
// is ready to be broadcast. | |
ChannelState::FundingNegotiated(flags) => { | |
if let Some(signing_session) = &self.funding_tx_signing_session { | |
signing_session.holder_tx_signatures().is_none() | |
} else { | |
!flags.is_interactive_signing() | |
} | |
}, |
Spotted by Diamond
Is this helpful? React 👍 or 👎 to let us know.
🔔 1st Reminder Hey @valentinewallace! This PR has been waiting for your review. |
9272b39
to
23a1c29
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #4061 +/- ##
==========================================
- Coverage 88.39% 87.84% -0.55%
==========================================
Files 177 176 -1
Lines 131314 131310 -4
Branches 131314 131310 -4
==========================================
- Hits 116069 115348 -721
- Misses 12596 13333 +737
+ Partials 2649 2629 -20
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
23a1c29
to
0cbeb51
Compare
lightning/src/ln/channel.rs
Outdated
debug_assert!( | ||
false, | ||
"A signing session must always be present while interactive signing" | ||
); | ||
let err = | ||
format!("Channel {} not expecting funding signatures", self.context.channel_id); | ||
return Err(APIError::APIMisuseError { err }); |
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 code contains a pattern where a debug assertion claims a signing session must always be present during interactive signing, but then includes error handling for when it's None
. This creates a logical contradiction - either:
- The assertion is correct and the error handling is unnecessary, or
- The error handling is necessary and the assertion is incorrect
This pattern appears multiple times throughout the code. Consider either:
- Removing the error handling if the assertion truly holds
- Adjusting the assertion to reflect the actual conditions where a signing session might be absent
This would make the code's intent clearer and avoid potential confusion about the expected state during interactive signing.
debug_assert!( | |
false, | |
"A signing session must always be present while interactive signing" | |
); | |
let err = | |
format!("Channel {} not expecting funding signatures", self.context.channel_id); | |
return Err(APIError::APIMisuseError { err }); | |
// A signing session should typically be present during interactive signing, | |
// but handle the case where it's not | |
let err = | |
format!("Channel {} not expecting funding signatures", self.context.channel_id); | |
return Err(APIError::APIMisuseError { err }); |
Spotted by Diamond
Is this helpful? React 👍 or 👎 to let us know.
🔔 1st Reminder Hey @TheBlueMatt @jkczyz! This PR has been waiting for your review. |
1 similar comment
🔔 1st Reminder Hey @TheBlueMatt @jkczyz! 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.
Will have to finish looking later
lightning/src/ln/channel.rs
Outdated
@@ -8655,56 +8642,51 @@ where | |||
#[rustfmt::skip] | |||
pub fn tx_signatures(&mut self, msg: &msgs::TxSignatures) -> Result<(Option<msgs::TxSignatures>, Option<Transaction>), ChannelError> { | |||
if !self.context.channel_state.is_interactive_signing() | |||
|| self.context.channel_state.is_their_tx_signatures_sent() | |||
&& !self.has_pending_splice_awaiting_signatures() |
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.
Why &&?
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.
Those are the only two states when we should be processing an incoming tx_signatures
.
lightning/src/ln/channel.rs
Outdated
// FIXME: Either we bring back the `OUR_TX_SIGNATURES_READY` flag for this specific | ||
// case, or we somehow access the signing session to check it 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.
Should we move this out of ChannelContext
then?
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.
Either that, or we track the session in the context, which lets us get rid of the interactive signing flag. I'm leaning towards the latter.
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.
or we track the session in the context, which lets us get rid of the interactive signing flag
Pushed an update that ended up doing this.
lightning/src/ln/channel.rs
Outdated
self.channel_state, | ||
ChannelState::ChannelReady(f) if f.is_set(ChannelReadyFlags::QUIESCENT) | ||
); | ||
debug_assert!(self.channel_state.is_interactive_signing() || is_quiescent); |
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.
For v2-channel establishment, is_quiescent
isn't applicable, IIUC. But for splicing, wouldn't checking is_interactive_signing
be sufficient?
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 interactive signing flag is only relevant to dual funding due to it being a flag on ChannelState::FundingNegotiated
.
The tests have become a bit painful to maintain, and they don't even fully test the production code paths, so we opt to just remove them for now. In the future, we plan to unify the dual funding code paths with splicing.
Since the `InteractiveTxSigningSession` already tracks everything we need, we can remove the duplicate interactive signing state tracking within `ChannelState`.
This commit addresses an overlap of state between `InteractiveTxSigningSession` and `ChannelState::FundingNegotiated`. The signing session already tracks whether both holder and counterparty `tx_signatures` have been produced, so tracking the state duplicatively at the `ChannelState` level is unnecessary.
Once a channel open has become locked (i.e., we've entered `ChannelState::ChannelReady`), the channel is intended to remain within this state for the rest of its lifetime until shutdown. Previously, we had assumed a channel being spliced would go through the `ChannelState` lifecycle again starting from `NegotiatingFunding` but skipping `AwaitingChannelReady`. This inconsistency departs from what we strive to achieve with `ChannelState` and also makes the state of a channel harder to reason about. This commit ensures a channel undergoing a splice remains in `ChannelReady`, clearing the quiescent flag once the negotiation is complete. Dual funding is unaffected by this change as the channel is being opened and we want to maintain the same `ChannelState` lifecycle.
0cbeb51
to
8ca96e3
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.
LGTM other some minor fixes
pending_splice.funding_negotiation.take() | ||
{ | ||
funding.funding_transaction = funding_tx_opt.clone(); | ||
self.pending_funding.push(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.
Oh, did we not do this yet?
if let Some(pending_splice) = self.pending_splice.as_mut() { | ||
if let Some(FundingNegotiation::AwaitingSignatures(mut funding)) = | ||
pending_splice.funding_negotiation.take() | ||
{ | ||
funding.funding_transaction = funding_tx_opt.clone(); | ||
self.pending_funding.push(funding); | ||
} else { | ||
debug_assert!(false, "We checked we were in the right state above"); | ||
} | ||
self.context.channel_state.clear_quiescent(); | ||
} else { | ||
self.funding.funding_transaction = funding_tx_opt.clone(); | ||
self.context.channel_state = | ||
ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::new()); | ||
} |
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.
nit: Might be worth DRYing this up
#[cfg(debug_assertions)] | ||
let sent_or_received_tx_signatures = self |
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.
CI is sad. Can't seem to see this for check_release
.
) -> (Option<MonitorUpdateCompletionAction>, Option<RAAMonitorUpdateBlockingAction>), | ||
) | ||
-> (Option<MonitorUpdateCompletionAction>, Option<RAAMonitorUpdateBlockingAction>), |
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.
CI isn't happy here and below. Think I've seen this sometimes where rustfmt
is nondeterministic.
This was pulled out of #4054 to ease review. It depends on #4060 to not bother with
cfg(splicing)
anymore.