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

[epic]: Dynamic Commitments (Dynamic Channels) #7878

Open
7 of 18 tasks
ProofOfKeags opened this issue Aug 8, 2023 · 10 comments · Fixed by #8026
Open
7 of 18 tasks

[epic]: Dynamic Commitments (Dynamic Channels) #7878

ProofOfKeags opened this issue Aug 8, 2023 · 10 comments · Fixed by #8026
Assignees
Labels
dynamic commitments enhancement Improvements to existing features / behaviour epic Issues created to track large feature development

Comments

@ProofOfKeags
Copy link
Collaborator

ProofOfKeags commented Aug 8, 2023

This issue is meant to be a living document to track the state of the Dynamic Commitments project.

The driving force behind the desire to have dynamic commitments is to allow us to "upgrade" an existing channel to use the Taproot Channels that are introduced in LND 0.17.0. The proposal has goals beyond simply upgrading channels to Taproot, but this is noted to prevent excessive scope creep and keep focused.

As part of the Dynamic Commitments work there is spec work that must be mostly complete before we embark on any implementation within LND.

  • Catch up on the state of Quiescence
  • Catch up on the state of Splicing
  • Finalize design goals and general approach
  • Formalize details in an Extension Bolt (Using this as a starting point)
  • Decide on whether or not we can do symmetric kickoff transactions!!!
  • Identify relevant subsystems that will need to be changed to accommodate new protocol
  • Implement changes to each of these subsystems
  • Leverage new capabilities in the subsystems to enable Taproot Channel upgrades
  • Decide on a GRPC API to execute the upgrade process

This issue will be updated to reflect the current state of the overall project.


Implementation Plan

@ProofOfKeags ProofOfKeags added the enhancement Improvements to existing features / behaviour label Aug 8, 2023
@saubyk saubyk added this to the v0.18.0 milestone Aug 9, 2023
@ProofOfKeags
Copy link
Collaborator Author

ProofOfKeags commented Aug 26, 2023

To keep the project focused, this is the list of the design goals in order of priority. With my current understanding of the problem set, it seems like it is possible (but still unknown) that not all of these goals is possible to realize simultaneously. Therefore, if it is found to be either impossible or prohibitively complex to implement, we will sacrifice later goals to accomplish earlier ones in the below list.

Design Goals

  1. Convert to Taproot Channels from pre-taproot channels WITHOUT spending pre-taproot channelpoint UTXO
  2. Modify all Channel Constraints that are originally set in open_channel and accept_channel
  3. Convert between all currently defined channel_types, including aforementioned STCs
  4. Convert between all possible channel_types (protocol cannot directly specify anything that implies knowledge of the output script that the post-conversion commitment transaction spends)
  5. Allow multiple Dynamic Channel conversions over the lifetime of the channel WITHOUT spending original channelpoint UTXO: the input space (the set of channel states that this protocol is capable of handling) must subsume the output space (the set of channel states this protocol is capable of producing)
    • This is currently the most at-risk requirement in our current design due to needing a revocation scheme for the "kickoff transaction"
  6. Permits easy extension to allow splicing during channel term renegotiation

In addition to this we will need to make sure that various protocol flows are satisfactorily immune to pinning issues, though I'm less sure about how to precisely put that in the priority list. After that we may wish to golf on certain transaction structures to save chain-space, round-trips, and wire-space.

CC @Roasbeef @saubyk please chime with any issues you have with this as a design target

@saubyk
Copy link
Collaborator

saubyk commented Aug 26, 2023

Thanks @ProofOfKeags for putting this list together. Couple of comments from my end.

Convert to Taproot Channels from pre-taproot channels WITHOUT spending pre-taproot channelpoint UTXO

This is for my education. Is it technically possible to change a pre-taproot channel to taproot one without spending the channel point UTXO?

Convert between all currently defined channel_types, including aforementioned STCs

Is there an advantage in building it in a general way making it possible to convert b/w any channel type? Also curious about the engineering complexity in making it general vis-a-vis a specific use case of converting option_static_remotekey/option_anchor_outputs to option_simple_taproot? Wondering if there would ever be a use case of converting from taproot to others and if the associated engineering complexity would be worth it

@ProofOfKeags
Copy link
Collaborator Author

Is it technically possible to change a pre-taproot channel to taproot one without spending the channel point UTXO?

Fundamentally what is required is being able to spend the legacy channelpoint UTXO into a Taproot channelpoint UTXO. However, this transaction doesn't need to be published. In fact, if it is published, without a wide deployment of Taproot aware (G175) implementations, then the channel will be rendered unusable by implementations that can't understand G175 channel announcements. So technically we are still "spending" the channelpoint UTXO, but this is not true from the perspective of the braoder network.

Is there an advantage in building it in a general way making it possible to convert b/w any channel type?

Prooooobably not? It depends on whether channel types that are developed later are always preferable to earlier ones. Even if they are, though, if we have a facility that can do arbitrary conversions, then even with a monotonically "better" channel type, being able to skip intermediate channel types could be desirable. All of this is theoretical though.

Also curious about the engineering complexity in making it general vis-a-vis a specific use case of converting option_static_remotekey/option_anchor_outputs to option_simple_taproot

My take on this is that designing a system to do one specific thing is the easiest, followed by designing a system to handle the general case. The largest maintenance burden is incurred when we design a system to handle a list of enumerated things and handle them in ways that reference the particulars of that case. This requires that we understand the properties of the existing system fairly well. However, the more we generalize the designs we create, the better we will understand those properties, creating a virtuous cycle.

Wondering if there would ever be a use case of converting from taproot to others and if the associated engineering complexity would be worth it

I sincerely doubt that from a user perspective this is ever going to be worth it. However, I suspect at this moment that a solution that allows for this case is one that will be far more future proof than one that takes advantage of the particulars of the channel types we currently use.

@saubyk saubyk added the epic Issues created to track large feature development label Aug 29, 2023
@Roasbeef
Copy link
Member

May be good to also unify some of the content in this issue as it's already identified some of the relevant integration/implementation sides. One example is the ChainWatcher recognizing that a force close can now occur if the kick off transaction hits the chain, or if the commitment does.

@Roasbeef
Copy link
Member

Design Goals

Convert to Taproot Channels from pre-taproot channels WITHOUT spending pre-taproot channelpoint UTXO
Modify all Channel Constraints that are originally set in open_channel and accept_channel

I think it's useful view the greater proposal as made up of two phases: expression and execution.

IMO from the get go, we'll want to make sure that just about everything we can think of can be expressed with the set of TLVs advertised. I think we're already most of the way there, but still need to catch up with the current BOLT draft.

Execution on the other hand is distinct in my mind as there're certain proposals that can be generically executed, eg: updating all the channel constraints, as then for the next state, the new constraints are applied. Others will require a new "execution module" for each possible transition. One example is going from static key to anchors vs anchors to taproot channels. For the sake of the channel type conversion, the only concrete transition I think we should be concerned with is: static key to taproot chans (or anchors for public chans*), anchors to taproot chans, and taproot chans' to taproot chans.

Allow multiple Dynamic Channel conversions over the lifetime of the channel WITHOUT spending original channelpoin
This is currently the most at-risk requirement in our current design due to needing a revocation scheme for the "kickoff transaction"

As we discussed offline, I don't think we need a revocation scheme for the kick off transaction, although if designing from the group up with no constraints w.r.t time, I think we'd add one in. IMO the ideal revocation scheme here uses the primitives from this paper, which needs musig2 adaptor sigs. We'll need this eventually for PTLC, but I don't think we need to go down that side quest at this moment.

Instead, we'd accept that when breaching the breacher needs to first broadcast the kick off, then go ahead and breach. I don't really see this as a grieving vector, as it just terminates after a single road: they broadcast 2 txs, then we enact justice. It isn't like eltoo where they can continue to broadcast the very next one, over and over again and waste our time. This is where the ChainWatcher will also be important: if the kick off transaction also encodes a sequence number in the locktime+sequence, then it can use this to detect a breach at the earliest possible point.

@ProofOfKeags
Copy link
Collaborator Author

I don't really see this as a grieving vector as it just terminates after a single road

In a world where we don't have a revocation for the kickoff itself, the griefing vector comes from the situation where they broadcast an old kickoff but do not broadcast an actual breach transaction. Sans breach transaction, we can't enact justice. However we also can't broadcast our own followup to the kickoff since it too has been revoked. This leaves money locked up in the kickoff output until either party decides to broadcast a revoked commitment built off of that old kickoff, which neither one has the incentive to do. The equilibrium here if we take the broadcast of an old kickoff as given is for neither party to broadcast the valid but revoked commitments they have built off of the kickoff, leaving the channelpoint buried.

Of course, in LN today, due to breach/justice mechanic a party always has the opportunity to nuke all of their own money by broadcasting a revoked commitment, however I don't believe there is a precedent for nuking your own money in order to nuke your counterparty's. If channel liquidity is heavily imbalanced then there could be a scenario when I nuke 1k sats to cause you to lose 100k sats.

I think it's useful view the greater proposal as made up of two phases: expression and execution.

This feels like a good delineation and we can get TLVs locked down pretty quickly I'm not so worried about that.

Others will require a new "execution module" for each possible transition.

Yeah I think we can also specify that if the implementation doesn't have a valid execution module for that transition then it can just bounce the dyncomm proposal at the beginning of the negotiation. Having an implementation that is incapable of a transition is not dissimilar from having a user unwilling to do a transition.

@ProofOfKeags
Copy link
Collaborator Author

I took a break from working out these details to finish up my first draft of the actual proposal language. That work can be tracked here

@ProofOfKeags
Copy link
Collaborator Author

During yesterday's design meeting we discussed the following items:

  1. The BOLT will remain a single document with two distinct sections: proposal and execution
  2. We will not bother with trying to copy the channel open flow and instead take a "lazier" approach where we address real world pain during initial implementation
  3. TLVs for all of the changing parameters will be specified in initial proposal, requirements will be written in a way which allows for partial implementation since responder can reject the negotiation for any reason
  4. Pubkey rotation is a precautionary step so that we do not do key reuse/sharing between ECDSA and Schnorr contexts, it will be required for channelpoint reanchoring
  5. Since push_msat isn't used very often, we won't really include it in this revision of the proposal, if we find we need to add it later we can
  6. The initiator of the DynComm upgrade will pay the fees, even if they were not the initial channel funder
  7. The kickoff needs anchors because the commitment transaction will not always be able to CPFP the kickoff
  8. During coop close we will spend the reanchored output rather than the original funding output. While this sacrifices chain efficiency it simplifies architecture/implementation
  9. We will not allow DynComm upgrades if there are any HTLCs in flight, we may be able to relax this in the future but for now keep it simple
  10. We will not treat a broadcast of a kickoff (alone) as a force closure. The chain_watcher will watch for the spend of the kickoff output
  11. SCBs need to be able to handle both the outpoints being spent, and also needs to track the new funding keys in addition to the old ones
  12. The kickoff is symmetric, however, we need to figure out how to negotiate the feerate here since a symmetric kickoff doesn't have an obvious revocation scheme

@dstadulis
Copy link
Collaborator

The initiator of the DynComm upgrade will pay the fees, even if they were not the initial channel funder

👍

@saubyk saubyk linked a pull request Oct 12, 2023 that will close this issue
7 tasks
@saubyk saubyk removed this from the v0.18.0 milestone Feb 6, 2024
@ProofOfKeags
Copy link
Collaborator Author

It's been a long time since I updated the status of this issue. So here is where we are at:

The spec draft is largely complete. Since we are not yet clear on how useful multiple funding output changes will be, we are in the immediate implementation plan going to block multiple dynamic commitment upgrades that would require funding output changes. This is a reasonable limitation to start out with. If we want to allow subsequent changes, since they will be infrequent we will not revoke prior kickoff transactions, nor will we cut-through them. Instead they will be chained until we decide that it is necessary to optimize this workflow. The odds are that YAGNI.

We are no proceeding with a number of CSM refactors that will allow us to insert the DynComm execution into the existing updateLog process. This is important because the CSM has been carefully designed to remain consistent in the presence of arbitrarily timed power failure. Since DynComms fundamentally is making a change to the CSM itself, and it is important to remember precisely when those CSM parameters changed with respect to the local and remote commitment heights, we need to make sure that we follow the same sequential processing that the current CSM does.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dynamic commitments enhancement Improvements to existing features / behaviour epic Issues created to track large feature development
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants