Skip to content

Commit

Permalink
Make splice_locked atomic on reestablish
Browse files Browse the repository at this point in the history
If one side sent `splice_locked` and the other side is ready to send
its own `splice_locked` while they are disconnected, this creates a
race condition on reestablish because `splice_locked` is retransmitted
after `channel_reestablish`, and other channel updates can be inserted
by the other node before receiving `splice_locked`. This will be an
issue for taproot channels, because nonces will be missing.
This race condition is described in more details in lightning#1223.

We fix this race condition by adding a TLV to `channel_reestablish`
that provides information about the latest locked transaction. This
additional information also makes it easier to detect when we need
to retransmit our previous `splice_locked`.
  • Loading branch information
t-bast committed Jan 28, 2025
1 parent 82f6862 commit 2c1b500
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 78 deletions.
58 changes: 30 additions & 28 deletions 02-peer-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -1944,7 +1944,7 @@ the locked transaction replaces the previous funding transaction.

Each node:
- If any splice transaction reaches acceptable depth:
- MUST send `splice_locked`.
- MUST send `splice_locked` with the `txid` of that transaction.

Once a node has sent and received `splice_locked`:
- MUST consider the locked splice transaction to be the new funding
Expand All @@ -1954,18 +1954,6 @@ Once a node has sent and received `splice_locked`:
- MUST send `announcement_signatures` with `short_channel_id` matching
the locked splice transaction.

On reconnection:
- MUST retransmit its last `splice_locked` if the `commitment_number`
is the same as before sending `splice_locked`.

##### Rationale

If a disconnection happens, nodes cannot know whether their peer received
their `splice_locked` message, so they retransmit it. Redundant messages
are harmless and can be safely ignored. If updates to the commitment have
been signed, this implicitly acknowledges that `splice_locked` has been
received and doesn't need to be retransmitted.

## Channel Close

Nodes can negotiate a mutual close of the connection, which unlike a
Expand Down Expand Up @@ -2938,6 +2926,9 @@ messages are), they are independent of requirements here.
1. type: 0 (`next_funding`)
2. data:
* [`sha256`:`next_funding_txid`]
1. type: 2 (`my_current_funding_locked`)
2. data:
* [`sha256`:`my_current_funding_locked_txid`]

`next_commitment_number`: A commitment number is a 48-bit
incrementing counter for each commitment transaction; counters
Expand Down Expand Up @@ -2996,6 +2987,13 @@ The sending node:
- MUST set `next_commitment_number` to the commitment number of the `commitment_signed` it sent.
- otherwise:
- MUST NOT set `next_funding_txid`.
- if `option_splice` was negotiated:
- MUST set `my_current_funding_locked` to the `funding_txid` of the most recent splice
transaction for which:
- it has already sent `splice_locked` or it is ready to send `splice_locked` after `channel_reestablish`.
- AND it has already received `splice_locked` from the remote node.
- if no splice transaction was ever made on the channel:
- MUST set `my_current_funding_locked` to the initial `funding_txid` of the channel.

A node:
- if `next_commitment_number` is 1 in both the `channel_reestablish` it
Expand Down Expand Up @@ -3064,11 +3062,16 @@ A receiving node:
this funding transaction.

A receiving node:
- MUST send `splice_locked` for the most recent splice transaction that reached
acceptable depth, unless `splice_locked` was already sent for this transaction
and `next_commitment_number` is strictly greater than the `commitment_number + 1`
that was used when `splice_locked` was previously sent (which indicates that
the previous `splice_locked` was received).
- if `my_current_funding_locked` is set:
- MUST process `my_current_funding_locked_txid` as if it was receiving
`splice_locked` for this `txid`, and thus discard the previous funding
transaction and RBF attempts if it has previously sent its own
`splice_locked` for that `txid`.
- if `my_current_funding_locked_txid` does not match the most recent
`splice_locked` it has sent:
- MUST retransmit `splice_locked`.
- otherwise:
- MUST NOT retransmit `splice_locked`.

A node:
- MUST NOT assume that previously-transmitted messages were lost,
Expand Down Expand Up @@ -3129,16 +3132,6 @@ operation, which is known to have begun after a `commitment_signed` has been
received — hence, the test for a `next_commitment_number` greater
than 1.

A previous draft insisted that the funder "MUST remember ...if it has
broadcast the funding transaction, otherwise it MUST NOT": this was in
fact an impossible requirement. A node must either firstly commit to
disk and secondly broadcast the transaction or vice versa. The new
language reflects this reality: it's surely better to remember a
channel which hasn't been broadcast than to forget one which has!
Similarly, for the fundee's `funding_signed` message: it's better to
remember a channel that never opens (and times out) than to let the
funder open it while the fundee has forgotten it.

A node, which has somehow fallen
behind (e.g. has been restored from old backup), can detect that it has fallen
behind. A fallen-behind node must know it cannot broadcast its current
Expand All @@ -3154,6 +3147,15 @@ interactive transaction construction, or safely abort that transaction
if it was not signed by one of the peers, who has thus already removed
it from its state.

`my_current_funding_locked` allows peers to detect that their `splice_locked`
was lost during the disconnection and must be retransmitted. When a splice
transaction reaches acceptable depth while peers are disconnected, it also
allows locking that splice transaction immediately after `channel_reestablish`
instead of waiting for the `splice_locked` message, which could otherwise
create a race condition with channel updates. For more details about this
race condition, see [this example](./bolt02/splicing-test.md#disconnection-with-concurrent-splice_locked).
Redundant `splice_locked` messages are harmless and can be safely ignored.

# Authors

[ FIXME: Insert Author List ]
Expand Down
66 changes: 16 additions & 50 deletions bolt02/splicing-test.md
Original file line number Diff line number Diff line change
Expand Up @@ -627,17 +627,17 @@ Alice initiates a splice, but disconnects before Bob receives her tx_signatures
### Disconnection with concurrent `splice_locked`

In this scenario, disconnections happen while nodes are exchanging `splice_locked`.
The `splice_locked` message must be retransmitted on reconnection until new commitments have been signed.
The `splice_locked` message must be retransmitted on reconnection if it wasn't previously received.
When `my_current_funding_locked` is set, this lets nodes immediately lock the latest splice transaction.

```text
Initial active commitments:
commitment_number = 10
+------------+
| FundingTx1 |
+------------+
Alice initiates a splice, but disconnects before Bob receives her splice_locked:
Alice initiates a splice, but disconnections happen when exchanging splice_locked:
Alice Bob
| stfu |
Expand Down Expand Up @@ -668,77 +668,43 @@ Alice initiates a splice, but disconnects before Bob receives her splice_locked:
|---------------------X |
| | Active commitments:
| |
| | commitment_number = 10
| | +------------+ +------------+
| | | FundingTx1 |------->| FundingTx2 |
| | +------------+ +------------+
| |
| channel_reestablish | next_funding_txid = null, next_commitment_number = 11, next_revocation_number = 10
| channel_reestablish | next_funding_txid = null, my_current_funding_locked_txid = funding_tx1
|----------------------------->|
| channel_reestablish | next_funding_txid = null, next_commitment_number = 11, next_revocation_number = 10
| channel_reestablish | next_funding_txid = null, my_current_funding_locked_txid = funding_tx1
|<-----------------------------|
| splice_locked |
|----------------------------->|
| splice_locked |
| splice_locked | At that point, Bob has locked funding_tx2, but Alice doesn't know it because she hasn't received splice_locked yet.
| X----------------------|
| |
| channel_reestablish | next_funding_txid = null, next_commitment_number = 11, next_revocation_number = 10
| channel_reestablish | next_funding_txid = null, my_current_funding_locked_txid = funding_tx1
|----------------------------->|
| channel_reestablish | next_funding_txid = null, next_commitment_number = 11, next_revocation_number = 10
|<-----------------------------|
| splice_locked |
|----------------------------->|
| splice_locked |
| channel_reestablish | next_funding_txid = null, my_current_funding_locked_txid = funding_tx2
|<-----------------------------|
| | Alice doesn't need to retransmit splice_locked, since Bob's my_current_funding_locked_txid indicates that he received it.
| | Bob's my_current_funding_locked_txid lets Alice know that Bob has locked funding_tx2 while they were disconnected and will send his splice_locked.
| | She can thus immediately lock it as well even though she hasn't received yet Bob's splice_locked.
| |
| | Active commitments:
| |
| | commitment_number = 10
| |
| | +------------+
| | | FundingTx2 |
| | +------------+
| update_add_htlc |
|----------------------X |
| commit_sig |
|----------------------X |
| |
| channel_reestablish | next_funding_txid = null, next_commitment_number = 11, next_revocation_number = 10
|----------------------------->|
| channel_reestablish | next_funding_txid = null, next_commitment_number = 11, next_revocation_number = 10
|<-----------------------------|
| splice_locked |
|----------------------------->|
| splice_locked |
|<-----------------------------|
| update_add_htlc |
|----------------------------->|
| commit_sig |
| commit_sig | Alice doesn't need to sent commit_sig for funding_tx1 since funding_tx2 was locked.
|----------------------------->|
| splice_locked | Bob's splice_locked is sent concurrently with Alice's update_add_htlc and commit_sig: this is fine.
|<-----------------------------|
| revoke_and_ack |
|<-----------------------------|
| commit_sig |
|<-----------------------------|
| revoke_and_ack |
|----------------------------->|
| | Active commitments:
| |
| | commitment_number = 11
| | +------------+
| | | FundingTx2 |
| | +------------+
| |
| | A new commitment was signed, implicitly acknowledging splice_locked.
| | We thus don't need to retransmit splice_locked on reconnection.
| update_add_htlc |
|----------------------X |
| commit_sig |
|----------------------X |
| |
| channel_reestablish | next_funding_txid = null, next_commitment_number = 12, next_revocation_number = 11
|----------------------------->|
| channel_reestablish | next_funding_txid = null, next_commitment_number = 12, next_revocation_number = 11
|<-----------------------------|
| update_add_htlc |
|----------------------------->|
| commit_sig |
|----------------------------->|
```

0 comments on commit 2c1b500

Please # to comment.