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 #1223.

We fix this race condition by adding TLVs to `channel_reestablish`
that provide 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 Feb 18, 2025
1 parent 43b5785 commit 5912705
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 76 deletions.
66 changes: 40 additions & 26 deletions 02-peer-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -1945,7 +1945,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 @@ -1955,18 +1955,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 @@ -3193,6 +3181,11 @@ messages are), they are independent of requirements here.
1. type: 0 (`next_funding`)
2. data:
* [`sha256`:`next_funding_txid`]
1. type: 1 (`your_last_funding_locked`)
2. data:
* [`sha256`:`your_last_funding_locked_txid`]
1. type: 3 (`my_current_funding_locked`)
* [`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 @@ -3251,6 +3244,21 @@ 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 `your_last_funding_locked` to the txid of the last `splice_locked` it received.
- if it never received `splice_locked` for any transaction, but it received `channel_ready`:
- MUST set `your_last_funding_locked` to the txid of the channel funding transaction.
- otherwise (it has never received `channel_ready` or `splice_locked`):
- MUST NOT set `your_last_funding_locked`.
- if a splice transaction reached acceptable depth while disconnected:
- MUST set `my_current_funding_locked` to the txid of the latest such transaction.
- MUST send `splice_locked` for that transaction after `channel_reestablish`.
- otherwise:
- MUST set `my_current_funding_locked` to the txid of the last `splice_locked` it sent.
- if it never sent `splice_locked` for any transaction, but it sent `channel_ready`:
- MUST set `my_current_funding_locked` to the txid of the channel funding transaction.
- otherwise (it has never sent `channel_ready` or `splice_locked`):
- MUST NOT set `my_current_funding_locked`.

A node:
- if `next_commitment_number` is 1 in both the `channel_reestablish` it

This comment has been minimized.

Copy link
@remyers

remyers Feb 28, 2025

  • if if option_splice was negotiated, and your_funding_locked is not set:
    • MUST retransmit channel_ready

This is needed for any future updates where we do not use next_commitment_number to determine whether or not to retransmit channel_ready. The current method is ambiguous and could mean that either there have been no channel updates or we did not receive channel_ready. Using the your_funding_locked tlv will NOT resend channel_ready if there have been no channel updates but channel_ready was received.

Expand Down Expand Up @@ -3311,16 +3319,23 @@ A receiving node:
- MUST send its `tx_signatures` for that funding transaction.
- if it has already received `tx_signatures` for that funding transaction:
- MUST send its `tx_signatures` for that funding transaction.
- if `next_funding_txid` matches the latest funding transaction:
- if that transaction has reached acceptable depth:
- MUST send `splice_locked`.
- if it also sets `next_funding_txid` in its own `channel_reestablish`, but the
values don't match:
- MUST send an `error` and fail the channel.
- otherwise:
- MUST send `tx_abort` to let the sending node know that they can forget
this funding transaction.

A receiving node:
- if `my_current_funding_locked` does not match the most recent `splice_locked`
it has received:
- MUST process `my_current_funding_locked` 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 `your_last_funding_locked` is not set, or if it does not match the most recent
`splice_locked` it has sent:
- MUST retransmit `splice_locked`.

A node:
- MUST NOT assume that previously-transmitted messages were lost,
- if it has sent a previous `commitment_signed` message:
Expand Down Expand Up @@ -3380,16 +3395,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 @@ -3405,6 +3410,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.

`last_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 `last_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, your_last_funding_locked = funding_tx1, my_current_funding_locked = funding_tx2
|----------------------------->|
| channel_reestablish | next_funding_txid = null, next_commitment_number = 11, next_revocation_number = 10
| channel_reestablish | next_funding_txid = null, your_last_funding_locked = funding_tx1, my_current_funding_locked = 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, your_last_funding_locked = funding_tx1, my_current_funding_locked = funding_tx2
|----------------------------->|
| channel_reestablish | next_funding_txid = null, next_commitment_number = 11, next_revocation_number = 10
|<-----------------------------|
| splice_locked |
|----------------------------->|
| splice_locked |
| channel_reestablish | next_funding_txid = null, your_last_funding_locked = funding_tx2, my_current_funding_locked = funding_tx2
|<-----------------------------|
| | Alice doesn't need to retransmit splice_locked, since Bob's your_last_funding_locked indicates that he received it.
| | Bob's my_current_funding_locked 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 5912705

Please sign in to comment.