Skip to content
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

[3/4] - lnwallet/chancloser: add new protofsm based RBF chan closer #8512

Open
wants to merge 5 commits into
base: protofsm
Choose a base branch
from

Conversation

Roasbeef
Copy link
Member

@Roasbeef Roasbeef commented Mar 1, 2024

Overview

In this PR, we add a new RBF based co-op channel close state machine. This uses the new protofsm package to define the states and transitions for the state machine. The new state machine is then also updated to become a peer.MsgEndpoint which allows us to use the new peer.MsgRouter to handle all message dispatch into the state machine. A series of new wrapper structs (taking care to always accept interfaces to decouple from the concrete types) are created to be able to initialize the environment needed by the new state machine.

This PR doesn't yet include integration within the peer struct. That will come in a follow up PR as we need some additional changes to be able to handle the two possible co-op types that will exist.

The diff might look somewhat large, but over half of it us unit tests, and a large portion of the non-test diff is the new state and event declarations.

State Machine Flow

The new state machine can be modeled with the following diagram:

---
title: Co-Op Close V2
---
stateDiagram-v2
    state CoopCloseV2 {
    [*] --> ChannelActive
    ChannelActive --> ShutdownPending: send_shutdown
    ChannelActive --> ShutdownPending: shutdown_received

	ShutdownPending --> ChannelFlushing: shutdown_received
    ShutdownPending --> ChannelFlushing: shutdown_complete
    
    ChannelFlushing --> ClosingNegotiation: channel_flushed

    state ClosingNegotiation {
		direction TB
        [*] --> LocalCloseStart
        [*] --> RemoteCloseStart
        
        LocalCloseStart --> LocalOfferSent: send_offer
        LocalOfferSent --> ClosePending: local_sig_received
        
        RemoteCloseStart --> ClosePending: offer_received
    }
    
    ClosingNegotiation --> ShutdownPending: send_shutdown
    ClosingNegotiation --> ShutdownPending: shutdown_received
   
    ClosingNegotiation --> CloseFin: txn_confirmation
    }
Loading

Remaining TODOs

  • unit tests for the state machine
  • commit the above diagram and a msg flow diagram to repo as documentation

Fixes #7091

@Roasbeef Roasbeef added channel closing Related to the closing of channels cooperatively and uncooperatively channel management The management of the nodes channels spec rbf labels Mar 1, 2024
@Roasbeef Roasbeef added this to the v0.18.0 milestone Mar 1, 2024
Copy link
Contributor

coderabbitai bot commented Mar 1, 2024

Important

Review skipped

Auto reviews are limited to specific labels.

Labels to auto review (1)
  • llm-review

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share
Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

github-actions bot commented Mar 1, 2024

Pull reviewers stats

Stats of the last 30 days for lnd:

User Total reviews Time to review Total comments
guggero
🥇
14
▀▀▀
1d 3h 52m
26
▀▀
yyforyongyu
🥈
9
▀▀
1d 16h 25m
8
ellemouton
🥉
8
▀▀
4d 8h 7m
25
▀▀
ProofOfKeags
6
1d 16h 3m
42
▀▀▀▀
bhandras
5
4d 10h 42m
5
Crypt-iQ
3
1d 21h 30m
8
Roasbeef
2
12h 16m
3
saubyk
2
8h 12m
0
morehouse
1
14d 21h 46m
▀▀▀▀
0
ziggie1984
1
10d 15h 59m
▀▀▀
2

@saubyk
Copy link
Collaborator

saubyk commented Mar 1, 2024

Hi @Roasbeef can you please explain the scenario where you're expecting the state to transition back from ClosingNegotiation to ShutdownPending?

Given that the channel flushing has already happened before the state moved to ClosingNegotiation, what would be the scenario where that step would need to be executed again?

@Roasbeef
Copy link
Member Author

Roasbeef commented Mar 1, 2024

@saubyk good question, so I need to update the diagram slightly, but you're right in that at that point the channel is already flushed. With the code that's checked in, there's a special fast path that'll transition from ChannelActive all the way to ClosingNegotiation if we detect that the channel is already flushed. It'll already be flushed if we're doing an RBF iteration, or if we're reconnecting and want to resume the closing process.

Here's the location of one of the fast path transitions:

So in that case we sent shutdown, received it, then rather than wait in the ChannelFlushing state, we'll emit an internal event to have us go directly to the ChannelNegotiation phase.

Also just to clarify, the way the protocol works is that when either side wants to do a new RBF iteration (RBF their closing txn), they send the shutdown message again. You effectively can drop all state, then act as if you're closing for the first time. Reading between the lines, I think yuo're right in that you could just send the signatures again, but perhaps one side wants to close to a new address, and that information is contained within teh shutdown message.

I kept things like this rather than introducing some new states to keep the amount of total states to a minimum, and re-use more of the existing transition logic. As an example, if they send a shutdown message to us, we still want to validate that they're using the same upfront shutdown address, etc.

@Roasbeef Roasbeef changed the base branch from peer-msg-router to protofsm March 5, 2024 05:56
@Roasbeef Roasbeef force-pushed the protofsm branch 2 times, most recently from fa12732 to 578288e Compare March 6, 2024 03:13
@Roasbeef Roasbeef changed the title lnwallet/chancloser: add new protofsm based RBF chan closer [3/4] -lnwallet/chancloser: add new protofsm based RBF chan closer Mar 8, 2024
@Roasbeef Roasbeef changed the title [3/4] -lnwallet/chancloser: add new protofsm based RBF chan closer [3/4] - lnwallet/chancloser: add new protofsm based RBF chan closer Mar 8, 2024
@saubyk saubyk modified the milestones: v0.18.0, v0.18.1 Mar 21, 2024
Copy link
Collaborator

@ProofOfKeags ProofOfKeags left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First pass. This seems to implement the CCV2 protocol as I understand it, without issue. I did find the code hard to review though due to having to track the semantics of protofsm. I believe aspects of protofsm can be simplified specifically in how it pertains to emitting events.

In my ideal world we would make simplifying protofsm a prerequisite for merging this, but as I understand it, the deadline trumps fixing protofsm? I'd like others' input here. What I like here is that all of the state transitions are very clearly pure and I don't have any difficulty tracking whether or not the implementation of the states/state transitions is valid. However, some of the other operational semantics in how it ferries events out of it into the surrounding context, specifically as it pertains to internal events, can be complex.

lnwallet/chancloser/rbf_coop_states.go Show resolved Hide resolved
lnwallet/chancloser/rbf_coop_transitions.go Outdated Show resolved Hide resolved

c.EarlyRemoteOffer = fn.Some(*msg)

// TODO(roasbeef): unit test!
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧐

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still needs to be done. Marking to remember.

Comment on lines +451 to +456
func validateShutdownScript(upfrontScript, peerScript lnwire.DeliveryAddress,
netParams *chaincfg.Params) error {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this signature change unrelated? I'm having a hard time figuring out how it relates to the rest of this commit.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking back, the change was to remove the usage of Disconnect. As it should be the job of the higher layer to decide if we need to disconnect or not, we already return a discernible error.

// then also the flushing event.
return &CloseStateTransition{
NextState: &ShutdownPending{
prevState: c,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible for us to memleak with this type of a construction? I see us building up an ever larger execution trace, here. I think that since this is the closing state machine, we will drive towards final termination so it's not particularly concerning, but I wonder what happens for much longer lived state machines.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question, I think assuming the root ref to the state machine (which in the next PR lives in a map in the peer) is eligible for collection, then the entire state machine would be.

TBH, I don't think prevState is even used anywhere. The original idea was so the state new the traversal path it took to arrive at the current location, but it isn't used at all anymore. The main idea was to use the struct to add some information re what the state transition graph looks like. I tried with some type param stuff, but it got hairy and made things more difficult to move.

Will consider just moving to comment template.

Comment on lines +845 to +842
// TODO(roasbeef): assert same one set based on type, will be
// invalid otherwise anyway?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah the sig won't validate if it isn't the same one we did.

// variant of the co-op close tx.
//
// TODO(roasbeef): db will only store one instance -- which is ok?
err = env.ChanObserver.MarkCoopBroadcasted(closeTx, true)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does true here signify?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it was locally initiated or not.

Comment on lines +1101 to +1098
default:

return &CloseStateTransition{
NextState: c,
}, nil
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not error here like we do elsewhere to mark invalid state transitions? I think we should stay consistent about whether we silently drop the transition or we return an error. I think mixing approaches will lead to confusion.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Further, this seems to imply that a new Shutdown here won't kick the process off again.

Comment on lines +48 to +50
if !r.isExpectedChanID(msg.ChannelID) {
return fn.None[ProtocolEvent]()
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

invert these inquiries and consolidate all of the None cases.

Comment on lines +414 to +417
// TODO(roasbeef): not working? need custom checker?
/*functionalOpt := mock.FunctionalOptions(
lnwallet.WithCustomSequence(sequence),
)*/
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧐

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heh this was that bug fix for taproot chans. With the latest version though, we'll use lock time instead.

In this commit, we add the initial set of states for the new protofsm
based rbf chan closer. A diagram outlining the new states and their
transitions can be found here:
https://gist.github.com/Roasbeef/acc4ff51b9dff127230228a05553cdfe.

Unlike the existing co-op close process, this co-op close can be
restarted at anytime if either side sends a shutdown message. From
there, we'll each obtain a new RBF'd version that can be re-broadcasted.

This commit creates the set of states, along with the environment that
our state machine will use to drive itself forward.
In this commit, we add the ability to specify a custom sequence for a
co-op close tx. This'll come in handy later as the new co-op close
process allows a party to set a custom sequence.
In this commit, we add the state transitions for the new protofsm based
RBF chan closer. The underlying protocol is a new asymmetric co-op close
process, wherein either side can initiate a chan closer, and use their
settled funds to pay for fees within the channel.
This'll allow us to treat the state machine as a MsgEndpoint, and have
the readHandler in the peer automatically send new messages to it.
@lightninglabs-deploy
Copy link

@yyforyongyu: review reminder
@Crypt-iQ: review reminder
@morehouse: review reminder
@Roasbeef, remember to re-request review from reviewers when ready

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
channel closing Related to the closing of channels cooperatively and uncooperatively channel management The management of the nodes channels P1 MUST be fixed or reviewed rbf spec
Projects
Status: In Progress
Development

Successfully merging this pull request may close these issues.

4 participants