-
Notifications
You must be signed in to change notification settings - Fork 494
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
zzz-extension-bolt: dynamic commitments #1026
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
# Extension Bolt ZZZ: Dynamic Commitments | ||
|
||
Authors: | ||
* Olaoluwa Osuntokun <[email protected]> | ||
* Eugene Siegel <[email protected]> | ||
|
||
Created: 2022-06-24 | ||
|
||
# Table of Contents | ||
|
||
- [Introduction](#introduction) | ||
|
||
- [Specification](#specification) | ||
* [Proposal Messages](#proposal-messages) | ||
+ [`dyn_begin_propose`](#-dyn_begin_propose-) | ||
+ [`dyn_propose`](#-dyn_propose-) | ||
+ [`dyn_propose_reply`](#-dyn_propose_reply-) | ||
* [Reestablish](#reestablish) | ||
+ [`channel_reestablish`](#-channel_reestablish-) | ||
|
||
# Introduction | ||
|
||
Dynamic commitments are a way to propose changing some static channel parameters that | ||
are initially set in the `open_channel` and `accept_channel` messages. This document | ||
outlines how this can be done on-the-fly with new messages. | ||
|
||
# Specification | ||
|
||
## Proposal Messages | ||
|
||
Three new messages are introduced that let each side propose what they want to | ||
change about the channel. | ||
|
||
### `dyn_begin_propose` | ||
|
||
This message is sent when a node wants to begin the dynamic commitment negotiation | ||
process. This is a signaling message, similar to `shutdown` in the cooperative close | ||
flow. | ||
|
||
1. type: 111 (`dyn_begin_propose`) | ||
2. data: | ||
* [`32*byte`:`channel_id`] | ||
* [`byte`: `begin_propose_flags`] | ||
|
||
Only the least-significant-bit of `begin_propose_flags` is defined, the `reject` bit. | ||
|
||
#### Requirements | ||
|
||
The sending node: | ||
- MUST set `channel_id` to a valid channel they have with the receipient. | ||
- MUST set undefined bits in `begin_propose_flags` to 0. | ||
- MUST set the `reject` bit in `begin_propose_flags` if they are rejecting the dynamic | ||
commitment negotiation request. | ||
- MUST NOT send `update_add_htlc` messages after sending this unless one of the following | ||
is true: | ||
- dynamic commitment negotiation has finished | ||
- a `dyn_begin_propose` with the `reject` bit has been received. | ||
- a reconnection has occurred. | ||
- MUST only send one `dyn_begin_propose` during a single negotiation. | ||
- MUST fail to forward additional incoming HTLCs from the peer. | ||
|
||
The receiving node: | ||
- if `channel_id` does not match an existing channel it has with the peer: | ||
- MUST close the connection. | ||
- if the `reject` bit is set, but it hasn't sent a `dyn_begin_propose`: | ||
- MUST send an `error` and fail the channel. | ||
- if an `update_add_htlc` is received after this point and negotiation hasn't finished or | ||
terminated: | ||
- MUST send an `error` and fail the channel. | ||
|
||
#### Rationale | ||
|
||
This has similar semantics to the `shutdown` message where the channel comes to a state | ||
where updates may only be removed. The `reject` bit is necessary to avoid having to reconnect | ||
in order to have a useable channel state again. | ||
|
||
### `dyn_propose` | ||
|
||
This message is sent when neither side owes the other either a `revoke_and_ack` or | ||
`commitment_signed` message and each side's commitment has no HTLCs. For now, only the | ||
`dust_limit_satoshis` and `recipients_new_self_delay` parameter are defined in negotiation. | ||
After negotiation completes, commitment signatures will use these parameters. | ||
|
||
1. type: 113 (`dyn_propose`) | ||
2. data: | ||
* [`32*byte`:`channel_id`] | ||
* [`dyn_propose_tlvs`:`tlvs`] | ||
|
||
1. `tlv_stream`: `dyn_propose_tlvs` | ||
2. types: | ||
1. type: 0 (`dust_limit_satoshis`) | ||
2. data: | ||
* [`u64`:`senders_new_dust_limit`] | ||
1. type: 2 (`to_self_delay`) | ||
2. data: | ||
* [`u16`:`recipients_new_self_delay`] | ||
|
||
#### Requirements | ||
|
||
The sending node: | ||
- MUST set `channel_id` to an existing one it has with the recipient. | ||
- MUST NOT set `dust_limit_satoshis` to a value that trims any currently untrimmed output | ||
on the commitment transaction. | ||
- MUST NOT send a `dyn_propose` if a prior one is waiting for `dyn_propose_reply`. | ||
- MUST remember its last sent `dyn_propose` parameters. | ||
- MUST send this message as soon as both side's commitment transaction is free of any HTLCs | ||
and both sides have sent `dyn_begin_propose`. | ||
|
||
The receiving node: | ||
- if `channel_id` does not match an existing channel it has with the sender: | ||
- MUST close the connection. | ||
- if it does not agree with a parameter: | ||
- MUST send a `dyn_propose_reply` with the `reject` bit set. | ||
- else: | ||
- MUST send a `dyn_propose_reply` without the `reject` bit set. | ||
|
||
#### Rationale | ||
|
||
The requirement to not allow trimming outputs is just to make the dynamic commitment flow as | ||
uninvasive as possible to the commitment transaction. When other fields are introduced, a | ||
similar requirement should be added such that the new fields don't conflict with the current | ||
commitment transaction (i.e. setting the `channel_reserve` too high). | ||
|
||
The requirement for a node to remember what it last _sent_ and for it to remember what it | ||
_accepted_ is necessary to recover on reestablish. See the reestablish section for more | ||
details. | ||
|
||
### `dyn_propose_reply` | ||
|
||
TODO - tell what params are rejected in tlvs? | ||
|
||
This message is sent in response to a `dyn_propose`. It may either accept or reject the | ||
`dyn_propose`. If it rejects a `dyn_propose`, it allows the counterparty to send another | ||
`dyn_propose` to try again. If for some reason, negotiation is taking too long, it is possible | ||
to exit this phase by reconnecting as long as the exiting node hasn't sent `dyn_propose_reply` | ||
without the `reject` bit. | ||
|
||
1. type: 115 (`dyn_propose_reply`) | ||
2. data: | ||
* [`32*byte`:`channel_id`] | ||
* [`byte`: `propose_reply_flags`] | ||
|
||
The least-significant bit of `propose_reply_flags` is defined as the `reject` bit. | ||
|
||
#### Requirements | ||
|
||
The sending node: | ||
- MUST set `channel_id` to a valid channel they have with the recipient. | ||
- MUST set undefined bits in `propose_reply_flags` to 0. | ||
- MUST set the `reject` bit in `propose_reply_flags` if they are rejecting the newest | ||
`dyn_propose`. | ||
- MUST NOT send this message if there is no outstanding `dyn_propose` from the counterparty. | ||
- if the `reject` bit is not set: | ||
- MUST remember the related `dyn_propose` parameters. | ||
|
||
The receiving node: | ||
- if `channel_id` does not match an existing channel it has with the peer: | ||
- MUST close the connection. | ||
- if there isn't an outstanding `dyn_propose` it has sent: | ||
- MUST send an `error` and fail the channel. | ||
- if the `reject` bit was set: | ||
- MUST forget its last sent `dyn_propose` parameters. | ||
|
||
A node: | ||
- once it has both sent and received `dyn_propose_reply` without the `reject` bit set: | ||
- MUST increment their `propose_height`. | ||
|
||
#### Rationale | ||
|
||
The `propose_height` starts at 0 for a channel and is incremented by 1 every time the | ||
dynamic commitment proposal phase completes for a channel. See the reestablish section | ||
for why this is needed. | ||
|
||
## Reestablish | ||
|
||
### `channel_reestablish` | ||
|
||
A new TLV that denotes the node's current `propose_height` is included. | ||
|
||
1. `tlv_stream`: `channel_reestablish_tlvs` | ||
2. types: | ||
1. type: 20 (`propose_height`) | ||
2. data: | ||
* [`u64`:`propose_height`] | ||
|
||
#### Requirements | ||
|
||
The sending node: | ||
- MUST set `propose_height` to the number of dynamic proposal negotiations it has | ||
completed. The point at which it is incremented is described in the `dyn_propose_reply` | ||
section. | ||
|
||
The receiving node: | ||
- if the received `propose_height` equals its own `propose_height`: | ||
- MUST forget any stored proposal state for `propose_height`+1 in case negotiation didn't | ||
complete. Can continue using the channel. | ||
- SHOULD forget any state that is unnecessary for heights <= `propose_height`. | ||
- if the received `propose_height` is 1 greater than its own `propose_height`: | ||
- if it does not have any remote parameters stored for the received `propose_height`: | ||
- MUST send an `error` and fail the channel. The remote node is either lying about the | ||
`propose_height` or the recipient has lost data since its not possible to advance the | ||
height without the recipient storing the remote's parameters. | ||
- resume using the channel with its last-sent `dyn_propose` and the stored remote | ||
`dyn_propose` parameters and increment its `propose_height`. | ||
- if the received `propose_height` is 1 less than its own `propose_height`: | ||
- resume using the channel with the new paramters. | ||
- else: | ||
- MUST send an `error` and fail the channel. State was lost. | ||
|
||
#### Rationale | ||
|
||
If both sides have sent and received `dyn_propose_reply` without the `reject` bit before the | ||
connection closed, it is simple to continue. If one side has sent and received | ||
`dyn_propose_reply` without the `reject` bit and the other side has only sent and not received | ||
`dyn_propose_reply` without the `reject` bit, the flow is recoverable on reconnection. This is | ||
because the side that hasn't received `dyn_propose_reply` has an implicit acknowledgement their | ||
proposal was accepted based on the `propose_height` in the reestablish message. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 it make sense to re-use #869 here? At a glance it can probably be quite trivially expanded to include some of the functionality being used here:
reject
field to the message tostfu
messagemessage_type
tlv that indicates why you're pausing the channel (eg, in this examplemessage_type=115/dyn_propose
)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 made it this way because 869 allows HTLC's on the commit tx, and I didn't want to have to think about a new dust limit trimming HTLCs -- but a general flow like 869 w/o htlc's could be useful here and probably other future flows
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.
So i think this is the way people are leaning and it makes sense, but we need to figure out the warning message bit where it contains a rejected tlv which indicates the fields and why they're rejected (high/low).
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 can take a stab at this, looked into it a while back. I think @rustyrussell's idea of bad field / bad value / suggested value will fit pretty well for this use case. Simple enough to repeat that field if we want to point out all the fields we don't like (although I agree that in practise the first one we encounter and don't like should be good enough)
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.
cool, feel free to request a review