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`.
33 KiB
Splicing Tests
This file details various splicing protocol flows. We detail the exact flow of messages for each scenario, and highlight several edge cases that must be correctly handled by implementations.
Table of Contents
- Terminology
- Test Vectors
- Successful single splice
- Multiple splices with concurrent
splice_locked
- Disconnection with one side sending
commit_sig
- Disconnection with both sides sending
commit_sig
- Disconnection with one side sending
tx_signatures
- Disconnection with both sides sending
tx_signatures
- Disconnection with both sides sending
tx_signatures
and channel updates - Disconnection with concurrent
splice_locked
Terminology
We call "active commitments" the set of valid commitment transactions to which updates (update_add_htlc
, update_fulfill_htlc
, update_fail_htlc
, update_fail_malformed_htlc
, update_fee
) must be applied.
While a funding transaction is not locked (ie splice_locked
hasn't been exchanged), updates must be valid for all active commitments.
When representing active commitments, we will only draw the corresponding funding transactions for simplicity. The related commitment transaction simply spends that funding transaction.
For example, the following diagram displays the active commitments when we have an unconfirmed splice (FundingTx2a
) and 2 RBF attempts for that splice (FundingTx2b
and FundingTx2c
).
We thus have 4 active commitments:
- the commitment spending
FundingTx1
- the commitments spending each splice transaction (
FundingTx2a
,FundingTx2b
andFundingTx2c
)
+------------+ +-------------+
| FundingTx1 |--------+------>| FundingTx2a |
+------------+ | +-------------+
|
| +-------------+
+------>| FundingTx2b |
| +-------------+
|
| +-------------+
+------>| FundingTx2c |
+-------------+
Peers must always agree on the set of active commitments, otherwise one side will expect signatures that the other side will not send, which will lead to force-closing the channel.
Test Vectors
In the protocol flows below, we omit the interactive-tx
messages that build the transaction.
The only interactive-tx
messages we explicitly list are the consecutive tx_complete
that mark the end of the interactive-tx
construction.
We also assume that both peers use the same commitment_number
for simplicity.
Successful single splice
Let's warm up with the simplest possible flow: a splice transaction that confirms without any disconnection.
Initial active commitments:
commitment_number = 10
+------------+
| FundingTx1 |
+------------+
Alice initiates a splice:
Alice Bob
| stfu |
|----------------------------->|
| stfu |
|<-----------------------------|
| splice_init |
|----------------------------->|
| splice_ack |
|<-----------------------------|
| |
| <interactive-tx> |
|<---------------------------->|
| |
| tx_complete |
|----------------------------->|
| tx_complete |
|<-----------------------------|
| commit_sig |
|----------------------------->|
| commit_sig |
|<-----------------------------|
| tx_signatures |
|----------------------------->|
| tx_signatures |
|<-----------------------------|
| | The channel is no longer quiescent at that point.
| | Active commitments:
| |
| | commitment_number = 10
| | +------------+ +------------+
| | | FundingTx1 |------->| FundingTx2 |
| | +------------+ +------------+
| |
| update_add_htlc | Alice and Bob use the channel while the splice transaction is unconfirmed.
|----------------------------->|
| update_add_htlc |
|----------------------------->|
| commit_sig | batch_size = 2, funding_txid = FundingTx1, commitment_number = 11
|----------------------------->|
| commit_sig | batch_size = 2, funding_txid = FundingTx2, commitment_number = 11
|----------------------------->|
| revoke_and_ack |
|<-----------------------------|
| commit_sig | batch_size = 2, funding_txid = FundingTx1, commitment_number = 11
|<-----------------------------|
| commit_sig | batch_size = 2, funding_txid = FundingTx2, commitment_number = 11
|<-----------------------------|
| revoke_and_ack |
|----------------------------->|
| | Active commitments:
| |
| | commitment_number = 11
| | +------------+ +------------+
| | | FundingTx1 |------->| FundingTx2 |
| | +------------+ +------------+
| |
| splice_locked | The splice transaction confirms.
|----------------------------->|
| splice_locked |
|<-----------------------------|
| | Active commitments:
| |
| | commitment_number = 11
| | +------------+
| | | FundingTx2 |
| | +------------+
| |
| update_add_htlc | Alice and Bob can use the channel and forget the previous FundingTx1.
|----------------------------->|
| commit_sig |
|----------------------------->|
| revoke_and_ack |
|<-----------------------------|
| commit_sig |
|<-----------------------------|
| revoke_and_ack |
|----------------------------->|
| | Active commitments:
| |
| | commitment_number = 12
| | +------------+
| | | FundingTx2 |
| | +------------+
Multiple splices with concurrent splice_locked
Since nodes have different views of the blockchain, they may send splice_locked
at slightly different times.
Moreover, nodes may send splice_locked
concurrently with other channel updates, in which case they will receive some commit_sig
messages for obsolete commitments.
This is fine: nodes know how many commit_sig
messages to expect thanks to the batch_size
field, and they can simply ignore commit_sig
messages for which the funding_txid
cannot be found in the active commitments.
Initial active commitments:
commitment_number = 10
+------------+
| FundingTx1 |
+------------+
Alice initiates a splice:
Alice Bob
| stfu |
|----------------------------->|
| stfu |
|<-----------------------------|
| splice_init |
|----------------------------->|
| splice_ack |
|<-----------------------------|
| |
| <interactive-tx> |
|<---------------------------->|
| |
| tx_complete |
|----------------------------->|
| tx_complete |
|<-----------------------------|
| commit_sig |
|----------------------------->|
| commit_sig |
|<-----------------------------|
| tx_signatures |
|----------------------------->|
| tx_signatures |
|<-----------------------------|
| | Active commitments:
| |
| | commitment_number = 10
| | +------------+ +-------------+
| | | FundingTx1 |------->| FundingTx2a |
| | +------------+ +-------------+
| |
| stfu |
|----------------------------->|
| stfu |
|<-----------------------------|
| tx_init_rbf | Alice RBFs the splice attempt.
|----------------------------->|
| tx_ack_rbf |
|<-----------------------------|
| |
| <interactive-tx> |
|<---------------------------->|
| |
| tx_complete |
|----------------------------->|
| tx_complete |
|<-----------------------------|
| commit_sig |
|----------------------------->|
| commit_sig |
|<-----------------------------|
| tx_signatures |
|----------------------------->|
| tx_signatures |
|<-----------------------------|
| | Active commitments:
| |
| | commitment_number = 10
| | +------------+ +-------------+
| | | FundingTx1 |---+--->| FundingTx2a |
| | +------------+ | +-------------+
| | |
| | | +-------------+
| | +--->| FundingTx2b |
| | +-------------+
| |
| update_add_htlc | Alice and Bob use the channel while the splice transactions are unconfirmed.
|----------------------------->|
| update_add_htlc |
|----------------------------->|
| commit_sig | batch_size = 3, funding_txid = FundingTx1, commitment_number = 11
|----------------------------->|
| commit_sig | batch_size = 3, funding_txid = FundingTx2a, commitment_number = 11
|----------------------------->|
| commit_sig | batch_size = 3, funding_txid = FundingTx2b, commitment_number = 11
|----------------------------->|
| revoke_and_ack |
|<-----------------------------|
| commit_sig | batch_size = 3, funding_txid = FundingTx1, commitment_number = 11
|<-----------------------------|
| commit_sig | batch_size = 3, funding_txid = FundingTx2a, commitment_number = 11
|<-----------------------------|
| commit_sig | batch_size = 3, funding_txid = FundingTx2b, commitment_number = 11
|<-----------------------------|
| revoke_and_ack |
|----------------------------->|
| | Active commitments:
| |
| | commitment_number = 11
| | +------------+ +-------------+
| | | FundingTx1 |---+--->| FundingTx2a |
| | +------------+ | +-------------+
| | |
| | | +-------------+
| | +--->| FundingTx2b |
| | +-------------+
| |
| splice_locked | splice_txid = FundingTx2a
|----------------------------->|
| update_add_htlc |
|----------------------------->|
| commit_sig | batch_size = 3, funding_txid = FundingTx1, commitment_number = 12 -> this message will be ignored by Bob since FundingTx2a will be locked before the end of the batch
|----------------------------->|
| splice_locked | splice_txid = FundingTx2a
|<-----------------------------|
| commit_sig | batch_size = 3, funding_txid = FundingTx2a, commitment_number = 12
|----------------------------->|
| commit_sig | batch_size = 3, funding_txid = FundingTx2b, commitment_number = 12 -> this message can be ignored by Bob since FundingTx2a has been locked
|----------------------------->|
| revoke_and_ack |
|<-----------------------------|
| commit_sig |
|<-----------------------------|
| revoke_and_ack |
|----------------------------->|
| | Active commitments:
| |
| | commitment_number = 12
| | +-------------+
| | | FundingTx2b |
| | +-------------+
Disconnection with one side sending commit_sig
In this scenario, a disconnection happens when one side has sent commit_sig
but not the other.
Initial active commitments:
commitment_number = 10
+------------+
| FundingTx1 |
+------------+
Alice initiates a splice, but disconnects before receiving Bob's tx_complete:
Alice Bob
| stfu |
|----------------------------->|
| stfu |
|<-----------------------------|
| splice_init |
|----------------------------->|
| splice_ack |
|<-----------------------------|
| |
| <interactive-tx> |
|<---------------------------->|
| |
| tx_complete |
|----------------------------->|
| tx_complete |
| X----------------------|
| commit_sig |
| X----------------------|
| | Active commitments for Alice:
| |
| | commitment_number = 10
| | +------------+
| | | FundingTx1 |
| | +------------+
| |
| | Active commitments for Bob:
| |
| | commitment_number = 10
| | +------------+ +------------+
| | | FundingTx1 |------->| FundingTx2 |
| | +------------+ +------------+
| |
| channel_reestablish | next_funding_txid = null, next_commitment_number = 11, next_revocation_number = 10
|----------------------------->|
| channel_reestablish | next_funding_txid = FundingTx2, next_commitment_number = 10, next_revocation_number = 10
|<-----------------------------|
| tx_abort |
|----------------------------->|
| tx_abort |
|<-----------------------------|
| | Bob can safely forget the splice attempt because he hasn't sent tx_signatures.
| | Active commitments for Alice and Bob:
| |
| | commitment_number = 10
| | +------------+
| | | FundingTx1 |
| | +------------+
Disconnection with both sides sending commit_sig
In this scenario, a disconnection happens when both sides have sent commit_sig
.
They are able to resume the signatures exchange on reconnection.
In this example, Bob is supposed to send tx_signatures
first.
Initial active commitments:
commitment_number = 10
+------------+
| FundingTx1 |
+------------+
Alice initiates a splice, but disconnects before receiving Bob's commit_sig:
Alice Bob
| stfu |
|----------------------------->|
| stfu |
|<-----------------------------|
| splice_init |
|----------------------------->|
| splice_ack |
|<-----------------------------|
| |
| <interactive-tx> |
|<---------------------------->|
| |
| tx_complete |
|----------------------------->|
| tx_complete |
|<-----------------------------|
| commit_sig |
|--------------------X |
| commit_sig |
| X----------------------|
| | Active commitments:
| |
| | commitment_number = 10
| | +------------+ +------------+
| | | FundingTx1 |------->| FundingTx2 |
| | +------------+ +------------+
| |
| channel_reestablish | next_funding_txid = FundingTx2, next_commitment_number = 10, next_revocation_number = 10
|----------------------------->|
| channel_reestablish | next_funding_txid = FundingTx2, next_commitment_number = 10, next_revocation_number = 10
|<-----------------------------|
| commit_sig |
|----------------------------->|
| commit_sig |
|<-----------------------------|
| tx_signatures |
|<-----------------------------|
| tx_signatures |
|----------------------------->|
| | Active commitments:
| |
| | commitment_number = 10
| | +------------+ +------------+
| | | FundingTx1 |------->| FundingTx2 |
| | +------------+ +------------+
Disconnection with one side sending tx_signatures
In this scenario, a disconnection happens when one side has sent tx_signatures
but not the other.
They are able to resume the signatures exchange on reconnection.
Initial active commitments:
commitment_number = 10
+------------+
| FundingTx1 |
+------------+
Alice initiates a splice, but disconnects before receiving Bob's tx_signatures:
Alice Bob
| stfu |
|----------------------------->|
| stfu |
|<-----------------------------|
| splice_init |
|----------------------------->|
| splice_ack |
|<-----------------------------|
| |
| <interactive-tx> |
|<---------------------------->|
| |
| tx_complete |
|----------------------------->|
| tx_complete |
|<-----------------------------|
| commit_sig |
|----------------------------->|
| commit_sig |
|<-----------------------------|
| tx_signatures |
| X----------------------|
| | Active commitments:
| |
| | commitment_number = 10
| | +------------+ +------------+
| | | FundingTx1 |------->| FundingTx2 |
| | +------------+ +------------+
| |
| channel_reestablish | next_funding_txid = FundingTx2, next_commitment_number = 11, next_revocation_number = 10
|----------------------------->|
| channel_reestablish | next_funding_txid = FundingTx2, next_commitment_number = 11, next_revocation_number = 10
|<-----------------------------|
| tx_signatures |
|<-----------------------------|
| tx_signatures |
|----------------------------->|
| | Active commitments:
| |
| | commitment_number = 10
| | +------------+ +------------+
| | | FundingTx1 |------->| FundingTx2 |
| | +------------+ +------------+
Disconnection with both sides sending tx_signatures
In this scenario, a disconnection happens when both sides have sent tx_signatures
, but one side has not received it.
They are able to resume the signatures exchange on reconnection.
Initial active commitments:
commitment_number = 10
+------------+
| FundingTx1 |
+------------+
Alice initiates a splice, but disconnects before Bob receives her tx_signatures:
Alice Bob
| stfu |
|----------------------------->|
| stfu |
|<-----------------------------|
| splice_init |
|----------------------------->|
| splice_ack |
|<-----------------------------|
| |
| <interactive-tx> |
|<---------------------------->|
| |
| tx_complete |
|----------------------------->|
| tx_complete |
|<-----------------------------|
| commit_sig |
|----------------------------->|
| commit_sig |
|<-----------------------------|
| tx_signatures |
|<-----------------------------|
| tx_signatures |
|----------------------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 = FundingTx2, next_commitment_number = 11, next_revocation_number = 10
|<-----------------------------|
| tx_signatures |
|----------------------------->|
| | Active commitments:
| |
| | commitment_number = 10
| | +------------+ +------------+
| | | FundingTx1 |------->| FundingTx2 |
| | +------------+ +------------+
Disconnection with both sides sending tx_signatures
and channel updates
In this scenario, a disconnection happens when both sides have sent tx_signatures
, but one side has not received it.
The second signer also sent a new signature for additional changes to apply after their tx_signatures
.
They are able to resume the signatures exchange on reconnection and retransmit new updates.
Initial active commitments:
commitment_number = 10
+------------+
| FundingTx1 |
+------------+
Alice initiates a splice, but disconnects before Bob receives her tx_signatures and new updates:
Alice Bob
| stfu |
|----------------------------->|
| stfu |
|<-----------------------------|
| splice_init |
|----------------------------->|
| splice_ack |
|<-----------------------------|
| |
| <interactive-tx> |
|<---------------------------->|
| |
| tx_complete |
|----------------------------->|
| tx_complete |
|<-----------------------------|
| commit_sig |
|----------------------------->|
| commit_sig |
|<-----------------------------|
| tx_signatures |
|<-----------------------------|
| tx_signatures |
|----------------------X |
| update_add_htlc |
|----------------------X |
| commit_sig | batch_size = 2, funding_txid = FundingTx1, commitment_number = 11
|----------------------X |
| commit_sig | batch_size = 2, funding_txid = FundingTx2, commitment_number = 11
|----------------------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 = FundingTx2, next_commitment_number = 11, next_revocation_number = 10
|<-----------------------------|
| tx_signatures |
|----------------------------->|
| update_add_htlc |
|----------------------------->|
| commit_sig | batch_size = 2, funding_txid = FundingTx1, commitment_number = 11
|----------------------------->|
| commit_sig | batch_size = 2, funding_txid = FundingTx2, commitment_number = 11
|----------------------------->|
| revoke_and_ack |
|<-----------------------------|
| commit_sig | batch_size = 2, funding_txid = FundingTx1, commitment_number = 11
|<-----------------------------|
| commit_sig | batch_size = 2, funding_txid = FundingTx2, commitment_number = 11
|<-----------------------------|
| revoke_and_ack |
|----------------------------->|
| | Active commitments:
| |
| | commitment_number = 11
| | +------------+ +------------+
| | | FundingTx1 |------->| FundingTx2 |
| | +------------+ +------------+
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 if it wasn't previously received.
When last_funding_locked
is set, this lets nodes immediately lock the latest splice transaction.
Initial active commitments:
+------------+
| FundingTx1 |
+------------+
Alice initiates a splice, but disconnections happen when exchanging splice_locked:
Alice Bob
| stfu |
|----------------------------->|
| stfu |
|<-----------------------------|
| splice_init |
|----------------------------->|
| splice_ack |
|<-----------------------------|
| |
| <interactive-tx> |
|<---------------------------->|
| |
| tx_complete |
|----------------------------->|
| tx_complete |
|<-----------------------------|
| commit_sig |
|----------------------------->|
| commit_sig |
|<-----------------------------|
| tx_signatures |
|<-----------------------------|
| tx_signatures |
|----------------------------->|
| splice_locked |
|---------------------X |
| | Active commitments:
| |
| | +------------+ +------------+
| | | FundingTx1 |------->| FundingTx2 |
| | +------------+ +------------+
| |
| channel_reestablish | next_funding_txid = null, your_last_funding_locked = funding_tx1, my_current_funding_locked = funding_tx2
|----------------------------->|
| channel_reestablish | next_funding_txid = null, your_last_funding_locked = funding_tx1, my_current_funding_locked = funding_tx1
|<-----------------------------|
| 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, your_last_funding_locked = funding_tx1, my_current_funding_locked = funding_tx2
|----------------------------->|
| 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:
| |
| | +------------+
| | | FundingTx2 |
| | +------------+
| |
| update_add_htlc |
|----------------------------->|
| 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 |
|----------------------------->|