From 43b5785da5b41e8adaed3503f1f205b50daaa1c4 Mon Sep 17 00:00:00 2001 From: t-bast Date: Thu, 2 May 2024 11:30:31 +0200 Subject: [PATCH 01/21] Channel Splicing (feature 62/63) Splicing allows spending the current funding transaction to replace it with a new one that changes the capacity of the channel, allowing both peers to add or remove funds to/from their channel balance. Splicing takes place while a channel is quiescent, to ensure that both peers have the same view of the current commitments. We don't want channels to be unusable while waiting for transactions to confirm, so channel operation returns to normal once the splice tx has been signed and we're waiting for it to confirm. The channel can then be used for payments, as long as those payments are valid for every pending splice transactions. Splice transactions can be RBF-ed to speed up confirmation. Once one of the pending splice transactions confirms and reaches acceptable depth, peers exchange `splice_locked` to discard the other pending splice transactions and the previous funding transaction. The confirmed splice transaction becomes the channel funding transaction. Nodes then advertize this spliced channel to the network, so that nodes keep routing payments through it without any downtime. --- 02-peer-protocol.md | 548 ++++++++++++++++++++++++++++- 07-routing-gossip.md | 6 +- 09-features.md | 1 + bolt02/splicing-test.md | 744 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1291 insertions(+), 8 deletions(-) create mode 100644 bolt02/splicing-test.md diff --git a/02-peer-protocol.md b/02-peer-protocol.md index 205772803..b1f7061a8 100644 --- a/02-peer-protocol.md +++ b/02-peer-protocol.md @@ -33,6 +33,11 @@ operation, and closing. * [Sharing funding signatures: `tx_signatures`](#sharing-funding-signatures-tx_signatures) * [Fee bumping: `tx_init_rbf` and `tx_ack_rbf`](#fee-bumping-tx_init_rbf-and-tx_ack_rbf) * [Channel Quiescence](#channel-quiescence) + * [Channel Splicing](#channel-splicing) + * [The `splice_init` Message](#the-splice_init-message) + * [The `splice_ack` Message](#the-splice_ack-message) + * [Splice Transaction Construction](#splice-transaction-construction) + * [Splice Completion](#splice-completion) * [Channel Close](#channel-close) * [Closing Initiation: `shutdown`](#closing-initiation-shutdown) * [Closing Negotiation: `closing_signed`](#closing-negotiation-closing_signed) @@ -200,6 +205,13 @@ This message contains a transaction input. * [`prevtx_len*byte`:`prevtx`] * [`u32`:`prevtx_vout`] * [`u32`:`sequence`] + * [`tx_add_input_tlvs`:`tlvs`] + +1. `tlv_stream`: `tx_add_input_tlvs` +2. types: + 1. type: 0 (`shared_input_txid`) + 2. data: + * [`sha256`:`funding_txid`] #### Requirements @@ -393,12 +405,19 @@ the byte size of the input and output counts on the transaction to one (1). * [`sha256`:`txid`] * [`u16`:`num_witnesses`] * [`num_witnesses*witness`:`witnesses`] + * [`tx_signatures_tlvs`:`tlvs`] 1. subtype: `witness` 2. data: * [`u16`:`len`] * [`len*byte`:`witness_data`] +1. `tlv_stream`: `tx_signatures_tlvs` +2. types: + 1. type: 0 (`shared_input_signature`) + 2. data: + * [`signature`:`signature`] + #### Requirements The sending node: @@ -1498,6 +1517,456 @@ channel state significantly more complex to maintain. This introduces the derivative property that it is impossible to batch multiple downstream protocols in the same quiescence session. +## Channel Splicing + +Splicing is the term given for replacing the funding transaction with +a new one. For simplicity, splicing takes place once a channel is +[quiescent](#channel-quiescence). + +Operation returns to normal once the splice transaction has been signed +(while waiting for one of the splice transactions to confirm), at which +point the channel isn't quiescent anymore. + +The splice is finally terminated when both sides send `splice_locked` +to indicate that one of the splice transactions reached acceptable depth. + + +-------+ +-------+ + | |--- splice_init -------------->| | + | A |<--------------- splice_ack ---| B | + | | | | + | |--- tx_add_input ------------->| | + | |<------------- tx_add_input ---| | + | |--- tx_add_input ------------->| | + | |<------------ tx_add_output ---| | + | |--- tx_add_output ------------>| | + | |<-------------- tx_complete ---| | + | |--- tx_add_output ------------>| | + | |<-------------- tx_complete ---| | + | |--- tx_complete -------------->| | + | | | | + | |--- commit_sig --------------->| | + | |<--------------- commit_sig ---| | + | |--- tx_signatures ------------>| | + | |<------------ tx_signatures ---| | + | | | | + | | | | + | | | | + | |--- update_add_htlc ---------->| | + | |--- commit_sig --------------->| | + | |--- commit_sig --------------->| | + | |<----------- revoke_and_ack ---| | + | |<--------------- commit_sig ---| | + | |<--------------- commit_sig ---| | + | |--- revoke_and_ack ----------->| | + | | | | + | | | | + | | | | + | |<-------------- tx_init_rbf ---| | + | |--- tx_ack_rbf --------------->| | + | |<------------- tx_add_input ---| | + | |--- tx_add_input ------------->| | + | |<------------- tx_add_input ---| | + | |--- tx_add_output ------------>| | + | |<------------ tx_add_output ---| | + | |--- tx_complete -------------->| | + | |<------------ tx_add_output ---| | + | |--- tx_complete -------------->| | + | |<-------------- tx_complete ---| | + | | | | + | |<--------------- commit_sig ---| | + | |--- commit_sig --------------->| | + | |--- tx_signatures ------------>| | + | |<------------ tx_signatures ---| | + | | | | + | | | | + | | | | + | |--- update_add_htlc ---------->| | + | |--- commit_sig --------------->| | + | |--- commit_sig --------------->| | + | |--- commit_sig --------------->| | + | |<----------- revoke_and_ack ---| | + | |<--------------- commit_sig ---| | + | |<--------------- commit_sig ---| | + | |<--------------- commit_sig ---| | + | |--- revoke_and_ack ----------->| | + | | | | + | | | | + | | | | + | |--- splice_locked ------------>| | + | |<------------ splice_locked ---| | + | | | | + | | | | + | | | | + | |--- update_add_htlc ---------->| | + | |--- commit_sig --------------->| | + | |<----------- revoke_and_ack ---| | + | |<--------------- commit_sig ---| | + | |--- revoke_and_ack ----------->| | + | | | | + +-------+ +-------+ + +### The `splice_init` Message + +1. type: 80 (`splice_init`) +2. data: + * [`channel_id`:`channel_id`] + * [`s64`:`funding_contribution_satoshis`] + * [`u32`:`funding_feerate_perkw`] + * [`u32`:`locktime`] + * [`point`:`funding_pubkey`] + * [`splice_init_tlvs`:`tlvs`] + +1. `tlv_stream`: `splice_init_tlvs` +2. types: + 1. type: 2 (`require_confirmed_inputs`) + +`funding_contribution_satoshis` is the amount the sender is adding to their +channel balance (splice-in) or removing from their channel balance (splice-out). + +#### Requirements + +The sending node: + - MUST NOT send `splice_init` if the channel is not quiescent. + - MUST NOT send `splice_init` if it is not the quiescence initiator. + - MUST NOT send `splice_init` before sending and receiving `channel_ready`. + - MUST NOT send `splice_init` while another splice is being negotiated. + - MUST NOT send `splice_init` if another splice has been negotiated but + `splice_locked` has not been sent and received. + - MUST NOT send `splice_init` if it has previously sent `shutdown`. + - If it is splicing funds out of the channel: + - MUST set `funding_contribution_satoshis` to a negative value matching + the amount that will be subtracted from its current channel balance. + - If it is splicing funds into the channel: + - MUST set `funding_contribution_satoshis` to a positive value matching + the amount that will be added to its current channel balance. + - If it requires the receiving node to only use confirmed inputs: + - MUST set `require_confirmed_inputs`. + - SHOULD use a different `funding_pubkey` than the one used for the + previous funding transaction. + +The receiving node: + - If the channel is not quiescent: + - MUST send a `warning` and close the connection or send an `error` + and fail the channel. + - If the sending node is not the quiescence initiator: + - MUST send a `warning` and close the connection or send an `error` + and fail the channel. + - If another splice is already being negotiated: + - MUST send a `warning` and close the connection or send an `error` + and fail the channel. + - If another splice has been negotiated but isn't locked yet: + - MUST send a `warning` and close the connection or send an `error` + and fail the channel. + - If it has received `shutdown`: + - MUST send a `warning` and close the connection or send an `error` + and fail the channel. + - If `funding_contribution_satoshis` is negative and its absolute value is + greater than the sending node's current channel balance: + - MUST send a `warning` and close the connection or send an `error` + and fail the channel. + - If it accepts the splice attempt: + - MUST respond with `splice_ack`. + - Otherwise (it rejects the splice): + - MUST respond with `tx_abort`. + +### The `splice_ack` Message + +1. type: 81 (`splice_ack`) +2. data: + * [`channel_id`:`channel_id`] + * [`s64`:`funding_contribution_satoshis`] + * [`point`:`funding_pubkey`] + * [`splice_ack_tlvs`:`tlvs`] + +1. `tlv_stream`: `splice_ack_tlvs` +2. types: + 1. type: 2 (`require_confirmed_inputs`) + +#### Requirements + +The sending node: + - SHOULD use a different `funding_pubkey` than the one used for the + previous funding transaction. + - MAY set `funding_contribution_satoshis` to `0` if they don't want + to contribute to the splice. + - If it requires the receiving node to only use confirmed inputs: + - MUST set `require_confirmed_inputs`. + +The receiving node: + - If it has sent `splice_init`: + - If `funding_contribution_satoshis` is negative and its absolute value is + greater than the sending node's current channel balance: + - MUST send a `warning` and close the connection or send an `error` + and fail the channel. + - If it accepts the splice attempt: + - MUST start an `interactive-tx` session to create the splice transaction. + - Otherwise: + - MUST reject the splice attempt by sending `tx_abort`. + - Otherwise (it has not sent `splice_init`): + - MUST send a `warning` and close the connection or send an `error` + and fail the channel. + +### Splice Transaction Construction + +The splice transaction is created using the +[Interactive Transaction Construction](#interactive-transaction-construction) +protocol, with the following additional requirements. + +#### The `tx_add_input` Message + +##### Requirements + +The sending node: + - If it is the splice initiator: + - MUST add the current channel input to the splice transaction by + sending `tx_add_input` with `shared_input_txid` containing the + `txid` of the latest funding transaction. + - MUST NOT include `prevtx` for that shared input. + - If the receiver set `require_confirmed_inputs` in `splice_init`, + `splice_ack`, `tx_init_rbf` or `tx_ack_rbf`: + - MUST NOT send a `tx_add_input` that contains an unconfirmed input. + +The receiving node: + - If `shared_input_txid` is set: + - If it doesn't match the `txid` of the latest funding transaction: + - MUST fail the negotiation by sending `tx_abort`. + +##### Rationale + +The splice transaction must spend the current channel funding output. The +splice initiator is responsible for adding that input to the transaction, +and pay the fees for its weight. Since both peers already have access to +the funding transaction, it is wasteful to transmit it in the `prevtx` +field. It may also exceed 65kB, which makes it impossible to include it +in `tx_add_input` anyway, so we only transmit its `txid`. + +#### The `tx_add_output` Message + +##### Requirements + +The sending node: + - If it is the splice initiator: + - MUST send at least one `tx_add_output`, which contains the new + channel's funding output based on the `funding_pubkey`s from + `splice_init` and `splice_ack`. + - MUST set the amount of that `tx_add_output` to the previous + channel capacity with the `funding_contribution_satoshis`s from + `splice_init` and `splice_ack` applied. + +##### Rationale + +The splice initiator is responsible for adding the new channel funding +output to the transaction and paying the fees for its weight. + +#### The `tx_complete` Message + +##### Requirements + +The receiving node: + - MUST compute the channel balance for each side by adding their respective + `funding_contribution_satoshis` to their previous channel balance. + - MUST fail the negotiation by sending `tx_abort` if: + - There is not exactly one input spending the current funding transaction. + - There is not exactly one channel funding output using the funding public + keys and funding contributions from `splice_init` and `splice_ack`. + - This is an RBF attempt and the transaction's total fees is less than + the last successfully negotiated splice transaction's fees. + - Either side has added an output other than the channel funding output + and the balance for that side is less than the channel reserve that + matches the new channel capacity. + +##### Rationale + +If a side does not meet the reserve requirements, that's OK: but if they take +funds out of the channel, they must ensure that they do meet them. If your peer +adds a massive amount to the channel, then you only have to add more reserve if +you want to contribute to the splice (and you can use `tx_remove_output` and/or +`tx_remove_input` part-way through if this happens). + +### The `commitment_signed` Message + +After exchanging `tx_complete`, both peers send `commitment_signed` to commit +to the splice transaction by creating a commitment transaction spending the +new channel funding output. + +The usual [`commitment_signed`](#committing-updates-so-far-commitment_signed) +requirements apply with the following additions. + +#### Requirements + +The sending node: + - MUST create a commitment transaction that spends the splice funding output and: + - Adds `funding_contribution_satoshis` from `splice_init` and `splice_ack` + to the main balance of their respective sender. + - Uses the same feerate as the existing commitment transaction. + - Uses the same `commitment_number` as the existing commitment transaction. + - Does not set the `batch` field. + - MUST send signatures for pending HTLCs. + - MUST remember the details of this splice transaction. + +The receiving node: + - MUST NOT respond with `revoke_and_ack`. + - If it has not already transmitted its `commitment_signed`: + - MUST send `commitment_signed`. + - If it should sign first, as specified in the [`tx_signatures` requirements](#the-tx_signatures-message): + - MUST send `tx_signatures`. + +On reconnection: + - If `next_funding_txid` matches the splice transaction: + - MUST retransmit `commitment_signed`. + +#### Rationale + +Once peers are ready to exchange commitment signatures, they must remember +the details of the splice transaction to allow resuming the signatures +exchange if a disconnection happens. + +#### The `tx_signatures` Message + +##### Requirements + +The sending node: + - MUST set `shared_input_signature` to a valid ECDSA signature for the + `tx_add_input` spending the previous channel funding output using the + `funding_pubkey` that matches this input. + +The receiving node: + - If `shared_input_signature` is not set: + - MUST send an `error` and fail the channel. + - If `shared_input_signature` is not valid or non-compliant with the + LOW-S-standard rule[LOWS](https://github.com/bitcoin/bitcoin/pull/6769): + - MUST send an `error` and fail the channel. + - MUST consider the channel no longer quiescent. + +On reconnection: + - If `next_funding_txid` matches the splice transaction: + - MUST retransmit `tx_signatures`. + +##### Rationale + +Spending the channel funding output requires a signature from both peers. +Each peer transmits its own signature, which allows creating a valid +witness for the shared input without adding an additional message. + +Once `tx_signatures` have been exchanged, the splice transaction can be +broadcast. The channel is no longer quiescent: normal operation can resume +while waiting for the transaction to confirm and `splice_locked` messages +to be exchanged. + +#### The `tx_init_rbf` Message + +##### Requirements + +The sending node: + - MUST NOT send `tx_init_rbf` if the channel is not quiescent. + - MUST NOT send `tx_init_rbf` if it is not the quiescence initiator. + - MAY send `tx_init_rbf` even if it is not the splice initiator. + - MUST NOT send `tx_init_rbf` if it has previously sent `splice_locked`. + - MAY set `funding_output_contribution` to a different value than the + `funding_contribution_satoshis` used in `splice_init` or `splice_ack`, + or in previous RBF attempts. + +The receiving node: + - If the channel is not quiescent: + - MUST send a `warning` and close the connection or send an `error` + and fail the channel. + - If the sending node is not the quiescence initiator: + - MUST send a `warning` and close the connection or send an `error` + and fail the channel. + - If the sender previously sent `splice_locked`: + - MUST send a `warning` and close the connection or send an `error` + and fail the channel. + - If `funding_output_contribution` is negative and its absolute value is + greater than the sending node's current channel balance: + - MUST send a `warning` and close the connection or send an `error` + and fail the channel. + +##### Rationale + +Splice transactions can be RBF-ed to react to changes in the mempool feerate. +We allow both nodes to initiate RBF, because any one of them may want to take +this opportunity to splice additional funds into or out of the channel without +waiting for the initial splice transaction to confirm. + +Since splice transactions always spend the current channel funding output, the +RBF attempts automatically double-spend each other. + +#### The `tx_ack_rbf` Message + +##### Requirements + +The sending node: + - MAY set `funding_output_contribution` to a different value than the + `funding_contribution_satoshis` used in `splice_init` or `splice_ack`, + or in previous RBF attempts. + +The receiving node: + - If `funding_output_contribution` is negative and its absolute value is + greater than the sending node's current channel balance: + - MUST send a `warning` and close the connection or send an `error` + and fail the channel. + +### Splice Completion + +Once splice transactions have been signed but haven't reached acceptable +depth, channel operations go back to normal and HTLCs can be exchanged, +with the caveat that payments must be valid for all splice transactions. + +Nodes keep track of multiple commitment transactions (one for the current +funding transaction and one for each splice transaction) and exchange +signatures for each of these commitment transactions. + +``` ++------------+ +-----------+ +| Funding Tx |---+--->| Commit Tx | ++------------+ | +-----------+ + | +-----------+ +-----------+ + +--->| Splice Tx |----------->| Commit Tx | + | +-----------+ +-----------+ + | +---------------+ +-----------+ + +--->| Splice RBF #1 |------->| Commit Tx | + | +---------------+ +-----------+ + | +---------------+ +-----------+ + +--->| Splice RBF #2 |------->| Commit Tx | + +---------------+ +-----------+ +``` + +The splice completes by exchanging `splice_locked` messages, at which point +the locked transaction replaces the previous funding transaction. + +#### The `splice_locked` Message + +1. type: 77 (`splice_locked`) +2. data: + * [`channel_id`:`channel_id`] + * [`sha256`:`splice_txid`] + +##### Requirements + +Each node: + - If any splice transaction reaches acceptable depth: + - MUST send `splice_locked`. + +Once a node has sent and received `splice_locked`: + - MUST consider the locked splice transaction to be the new funding + transaction for all future `commitment_signed` messages and splice + negotiations. + - SHOULD discard the previous funding transaction and RBF attempts. + - 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 @@ -1608,6 +2077,7 @@ A sending node: - if there are updates pending on the receiving node's commitment transaction: - MUST NOT send a `shutdown`. - MUST NOT send multiple `shutdown` messages. + - MUST NOT send `shutdown` if there is a splice transaction that isn't locked yet. - MUST NOT send an `update_add_htlc` after a `shutdown`. - if no HTLCs remain in either commitment transaction (including dust HTLCs) and neither side has a pending `revoke_and_ack` to send: @@ -2271,6 +2741,8 @@ A sending node: - MUST increase the value of `id` by 1 for each successive offer. - if it is relaying a payment inside a blinded route: - MUST set `path_key` (see [Route Blinding](04-onion-routing.md#route-blinding)) + - if a splice is pending: + - MUST ensure that requirements are met for all commmitment transactions. `id` MUST NOT be reset to 0 after the update is complete (i.e. after `revoke_and_ack` has been received). It MUST continue incrementing instead. @@ -2302,6 +2774,8 @@ A receiving node: - MUST respond with an error as detailed in [Failure Messages](04-onion-routing.md#failure-messages) - Otherwise: - MUST follow the requirements for the reader of `payload` in [Payload Format](04-onion-routing.md#payload-format) + - If a splice is pending: + - MUST ensure that requirements are met for all commmitment transactions. The `onion_routing_packet` contains an obfuscated list of hops and instructions for each hop along the path. It commits to the HTLC by setting the `payment_hash` as associated data, i.e. includes the `payment_hash` in the computation of HMACs. @@ -2337,6 +2811,9 @@ maintaining its channel reserve (because of the increased weight of the commitment transaction), resulting in a degraded channel. See [#728](https://github.com/lightningnetwork/lightning-rfc/issues/728) for more details. +If splicing is supported, there can be more than one commitment transaction +at a time: proposed changes must be valid for all of them. + ### Removing an HTLC: `update_fulfill_htlc`, `update_fail_htlc`, and `update_fail_malformed_htlc` For simplicity, a node can only remove HTLCs added by the other node. @@ -2451,6 +2928,14 @@ sign the resulting transaction (as defined in [BOLT #3](03-transactions.md)), an * [`signature`:`signature`] * [`u16`:`num_htlcs`] * [`num_htlcs*signature`:`htlc_signature`] + * [`commitment_signed_tlvs`:`tlvs`] + +1. `tlv_stream`: `commitment_signed_tlvs` +2. types: + 1. type: 0 (`batch`) + 2. data: + * [`u16`:`batch_size`] + * [`sha256`:`funding_txid`] #### Requirements @@ -2467,6 +2952,13 @@ fee changes). to the ordering of the commitment transaction (see [BOLT #3](03-transactions.md#transaction-input-and-output-ordering)). - if it has not recently received a message from the remote node: - SHOULD use `ping` and await the reply `pong` before sending `commitment_signed`. + - If there are `N` pending splice transactions: + - MUST send `commitment_signed` for the current channel funding output. + - MUST send `commitment_signed` for each of the splice transactions. + - MUST set `batch_size` to `N + 1` in every `commitment_signed` message. + - MUST set `funding_txid` to the funding transaction spent by that commitment. + - Otherwise: + - MUST NOT include the `batch` field. A receiving node: - once all pending updates are applied: @@ -2480,7 +2972,25 @@ A receiving node: - if any `htlc_signature` is not valid for the corresponding HTLC transaction OR non-compliant with LOW-S-standard rule [LOWS](https://github.com/bitcoin/bitcoin/pull/6769): - MUST send a `warning` and close the connection, or send an `error` and fail the channel. - - MUST respond with a `revoke_and_ack` message. + - If there are pending splice transactions and `batch` is not set: + - MUST send an `error` and fail the channel. + - If `batch` is set: + - MUST wait until it has received `batch_size` messages. + - If there are pending splice transactions: + - MUST validate each `commitment_signed` based on `funding_txid`. + - If `commitment_signed` is missing for a funding transaction: + - MUST send an `error` and fail the channel. + - Otherwise: + - MUST respond with a `revoke_and_ack` message. + - Otherwise (no pending splice transactions): + - MUST ignore `commitment_signed` where `funding_txid` does not match + the current funding transaction. + - If `commitment_signed` is missing for the current funding transaction: + - MUST send an `error` and fail the channel. + - Otherwise: + - MUST respond with a `revoke_and_ack` message. + - Otherwise: + - MUST respond with a `revoke_and_ack` message. #### Rationale @@ -2503,6 +3013,13 @@ stating time-locks on HTLC outputs. The `option_anchors` allows HTLC transactions to "bring their own fees" by attaching other inputs and outputs, hence the modified signature flags. +Splicing requires us to send and receive additional signatures, as we don't +know which (if any) of the splice transactions will end up being the new +channel funding transaction. We send `commitment_signed` for each of the +pending splice transactions and for the current funding transaction. When +sending `splice_locked`, we may receive obsolete `commitment_signed` from +our peer: we can safely ignore them by filtering on `funding_txid`. + ### Completing the Transition to the Updated State: `revoke_and_ack` Once the recipient of `commitment_signed` checks the signature and knows @@ -2530,6 +3047,8 @@ A sending node: the previous commitment transaction. - MUST set `next_per_commitment_point` to the values for its next commitment transaction. + - MUST send a single `revoke_and_ack` message, even if it is responding to + a `batch` of `commitment_signed` messages. A receiving node: - if `per_commitment_secret` is not a valid secret key or does not generate the previous `per_commitment_point`: @@ -2585,6 +3104,8 @@ A sending node: - if the dust balance of the local transaction at the updated `feerate_per_kw` is greater than `max_dust_htlc_exposure_msat`: - MAY NOT send `update_fee` - MAY fail the channel + - if a splice is pending: + - MUST ensure that requirements are met for all commmitment transactions. A receiving node: - if the `update_fee` is too low for timely processing, OR is unreasonably large: @@ -2604,6 +3125,8 @@ A receiving node: - MAY fail the channel - if the dust balance of the local transaction at the updated `feerate_per_kw` is greater than `max_dust_htlc_exposure_msat`: - MAY fail the channel + - if a splice is pending: + - MUST ensure that requirements are met for all commmitment transactions. #### Rationale @@ -2632,6 +3155,9 @@ be trimmed at the updated feerate, this could overflow the configured `max_dust_htlc_exposure_msat`. Whether to close the channel preemptively or not is left as a matter of node policy. +If splicing is supported, there can be more than one commitment transaction +at a time: proposed changes must be valid for all of them. + ## Message Retransmission Because communication transports are unreliable, and may need to be @@ -2721,6 +3247,8 @@ The sending node: - if it has sent `commitment_signed` for an interactive transaction construction but it has not received `tx_signatures`: - MUST set `next_funding_txid` to the txid of that interactive transaction. + - if it has not received `commitment_signed` for that interactive transaction: + - MUST set `next_commitment_number` to the commitment number of the `commitment_signed` it sent. - otherwise: - MUST NOT set `next_funding_txid`. @@ -2773,14 +3301,22 @@ A node: A receiving node: - if `next_funding_txid` is set: - - if `next_funding_txid` matches the latest interactive funding transaction: - - if it has not received `tx_signatures` for that funding transaction: + - if `next_funding_txid` matches the latest interactive funding transaction + or the current channel funding transaction: + - if `next_commitment_number` is equal to the commitment number of the + `commitment_signed` message it sent for this funding transaction: - MUST retransmit its `commitment_signed` for that funding transaction. - - if it has already received `commitment_signed` and it should sign first, - as specified in the [`tx_signatures` requirements](#the-tx_signatures-message): - - MUST send its `tx_signatures` for that funding transaction. + - if it has already received `commitment_signed` and it should sign first, + as specified in the [`tx_signatures` requirements](#the-tx_signatures-message): + - 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. diff --git a/07-routing-gossip.md b/07-routing-gossip.md index e306b6788..972bb8a49 100644 --- a/07-routing-gossip.md +++ b/07-routing-gossip.md @@ -84,8 +84,10 @@ matching an endpoint's `node_id` and `bitcoin_key`. A node: - If the `open_channel` message has the `announce_channel` bit set AND a `shutdown` message has not been sent: - - After `channel_ready` has been sent and received AND the funding transaction has enough confirmations to ensure that it won't be reorganized: - - MUST send `announcement_signatures` for the funding transaction. + - MUST NOT send `announcement_signatures` until `channel_ready` or `splice_locked` has been sent and received for that transaction. + - MUST NOT send `announcement_signatures` until the funding transaction has enough confirmations to ensure that it won't be reorganized. + - Otherwise: + - MUST send the `announcement_signatures` message. - Otherwise: - MUST NOT send the `announcement_signatures` message. - Upon reconnection (once the above timing requirements have been met): diff --git a/09-features.md b/09-features.md index 5aa651b55..23187bc16 100644 --- a/09-features.md +++ b/09-features.md @@ -53,6 +53,7 @@ The Context column decodes as follows: | 48/49 | `option_payment_metadata` | Payment metadata in tlv record | 9 | | [BOLT #11](11-payment-encoding.md#tagged-fields) | | 50/51 | `option_zeroconf` | Understands zeroconf channel types | IN | `option_scid_alias` | [BOLT #2][bolt02-channel-ready] | | 60/61 | `option_simple_close` | Simplified closing negotiation | IN | `option_shutdown_anysegwit` | [BOLT #2][bolt02-simple-close] | +| 62/63 | `option_splice` | Allows replacing the funding transaction with a new one | IN | | [BOLT #2](02-peer-protocol.md#channel-splicing) | ## Requirements diff --git a/bolt02/splicing-test.md b/bolt02/splicing-test.md new file mode 100644 index 000000000..859de6b22 --- /dev/null +++ b/bolt02/splicing-test.md @@ -0,0 +1,744 @@ +# Splicing Tests + +This file details various [splicing](../02-peer-protocol.md#channel-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](#terminology) +* [Test Vectors](#test-vectors) + * [Successful single splice](#successful-single-splice) + * [Multiple splices with concurrent `splice_locked`](#multiple-splices-with-concurrent-splice_locked) + * [Disconnection with one side sending `commit_sig`](#disconnection-with-one-side-sending-commit_sig) + * [Disconnection with both sides sending `commit_sig`](#disconnection-with-both-sides-sending-commit_sig) + * [Disconnection with one side sending `tx_signatures`](#disconnection-with-one-side-sending-tx_signatures) + * [Disconnection with both sides sending `tx_signatures`](#disconnection-with-both-sides-sending-tx_signatures) + * [Disconnection with both sides sending `tx_signatures` and channel updates](#disconnection-with-both-sides-sending-tx_signatures-and-channel-updates) + * [Disconnection with concurrent `splice_locked`](#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` and `FundingTx2c`) + +```text ++------------+ +-------------+ +| 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. + +```text +Initial active commitments: + + commitment_number = 10 + +------------+ + | FundingTx1 | + +------------+ + +Alice initiates a splice: + + Alice Bob + | stfu | + |----------------------------->| + | stfu | + |<-----------------------------| + | splice_init | + |----------------------------->| + | splice_ack | + |<-----------------------------| + | | + | | + |<---------------------------->| + | | + | 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. + +```text +Initial active commitments: + + commitment_number = 10 + +------------+ + | FundingTx1 | + +------------+ + +Alice initiates a splice: + + Alice Bob + | stfu | + |----------------------------->| + | stfu | + |<-----------------------------| + | splice_init | + |----------------------------->| + | splice_ack | + |<-----------------------------| + | | + | | + |<---------------------------->| + | | + | 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 | + |<-----------------------------| + | | + | | + |<---------------------------->| + | | + | 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. + +```text +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 | + |<-----------------------------| + | | + | | + |<---------------------------->| + | | + | 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. + +```text +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 | + |<-----------------------------| + | | + | | + |<---------------------------->| + | | + | 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. + +```text +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 | + |<-----------------------------| + | | + | | + |<---------------------------->| + | | + | 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. + +```text +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 | + |<-----------------------------| + | | + | | + |<---------------------------->| + | | + | 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. + +```text +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 | + |<-----------------------------| + | | + | | + |<---------------------------->| + | | + | 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 until new commitments have been signed. + +```text +Initial active commitments: + + commitment_number = 10 + +------------+ + | FundingTx1 | + +------------+ + +Alice initiates a splice, but disconnects before Bob receives her splice_locked: + + Alice Bob + | stfu | + |----------------------------->| + | stfu | + |<-----------------------------| + | splice_init | + |----------------------------->| + | splice_ack | + |<-----------------------------| + | | + | | + |<---------------------------->| + | | + | tx_complete | + |----------------------------->| + | tx_complete | + |<-----------------------------| + | commit_sig | + |----------------------------->| + | commit_sig | + |<-----------------------------| + | tx_signatures | + |<-----------------------------| + | tx_signatures | + |----------------------------->| + | 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, next_commitment_number = 11, next_revocation_number = 10 + |<-----------------------------| + | splice_locked | + |----------------------------->| + | splice_locked | + | 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 | + |<-----------------------------| + | | 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 | + |----------------------------->| + | 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 | + |----------------------------->| +``` From 4ce4b9c3b1ea840f139479b5c9b0d90702164485 Mon Sep 17 00:00:00 2001 From: t-bast Date: Fri, 24 Jan 2025 10:33:33 +0100 Subject: [PATCH 02/21] Make `splice_locked` atomic on reestablish 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`. --- 02-peer-protocol.md | 72 +++++++++++++++++++++++++---------------- bolt02/splicing-test.md | 66 +++++++++---------------------------- 2 files changed, 61 insertions(+), 77 deletions(-) diff --git a/02-peer-protocol.md b/02-peer-protocol.md index b1f7061a8..ee9739625 100644 --- a/02-peer-protocol.md +++ b/02-peer-protocol.md @@ -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 @@ -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 @@ -3193,6 +3181,12 @@ 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`) + 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 @@ -3251,10 +3245,28 @@ 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 - sent and received: + sent and received and `option_splice` was NOT negotiated: + - MUST retransmit `channel_ready`. + - if `option_splice` was negotiated and `your_last_funding_locked` is not + set in the `channel_reestablish` it received: - MUST retransmit `channel_ready`. - otherwise: - MUST NOT retransmit `channel_ready`, but MAY send `channel_ready` with @@ -3311,9 +3323,6 @@ 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. @@ -3321,6 +3330,16 @@ A receiving node: - 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: @@ -3380,16 +3399,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 @@ -3405,6 +3414,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. +`your_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 ] diff --git a/bolt02/splicing-test.md b/bolt02/splicing-test.md index 859de6b22..2db574839 100644 --- a/bolt02/splicing-test.md +++ b/bolt02/splicing-test.md @@ -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 | @@ -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 | - |----------------------------->| ``` From aa0307640c0a0d97b4e2eb1f1a59c33d96d82815 Mon Sep 17 00:00:00 2001 From: t-bast Date: Wed, 12 Feb 2025 12:22:19 +0100 Subject: [PATCH 03/21] Clarify announcement requirements We make the requirements for `announcement_signatures` more clear. It is important that both nodes are able to generate the corresponding `channel_announcement` to allow them to create a new `channel_update` using the `short_channel_id` of the confirmed splice. We insist on exchanging `splice_locked` before generating signatures to ensure compatibility with taproot channels, where nonces will be exchanged in `splice_locked` messages. This means that we need to retransmit `splice_locked` on reconnection if `announcement_signatures` hasn't been fully exchanged. Importantly, after announcing a splice, nodes must still allow payments that use the previous `short_channel_id`, because remote nodes may not have processed the `channel_announcement` and `channel_update`s yet. --- 02-peer-protocol.md | 7 ++++++ 07-routing-gossip.md | 51 +++++++++++++++++++++++++++++++------------- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/02-peer-protocol.md b/02-peer-protocol.md index ee9739625..0a408d7ee 100644 --- a/02-peer-protocol.md +++ b/02-peer-protocol.md @@ -3260,6 +3260,13 @@ The sending node: - 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`. + - if `my_current_funding_locked` is included and `announce_channel` is set for this channel: + - if it has not received `announcement_signatures` for that transaction: + - MUST retransmit `channel_ready` or `splice_locked` after `channel_reestablish`. + - if it receives `channel_ready` for that transaction after `channel_reestablish`: + - MUST retransmit `channel_ready` in response, if not already sent. + - if it receives `splice_locked` for that transaction after `channel_reestablish`: + - MUST retransmit `splice_locked` in response, if not already sent. A node: - if `next_commitment_number` is 1 in both the `channel_reestablish` it diff --git a/07-routing-gossip.md b/07-routing-gossip.md index 972bb8a49..cc41ec248 100644 --- a/07-routing-gossip.md +++ b/07-routing-gossip.md @@ -84,10 +84,10 @@ matching an endpoint's `node_id` and `bitcoin_key`. A node: - If the `open_channel` message has the `announce_channel` bit set AND a `shutdown` message has not been sent: - - MUST NOT send `announcement_signatures` until `channel_ready` or `splice_locked` has been sent and received for that transaction. - - MUST NOT send `announcement_signatures` until the funding transaction has enough confirmations to ensure that it won't be reorganized. - - Otherwise: - - MUST send the `announcement_signatures` message. + - After `channel_ready` has been sent and received AND the funding transaction has enough confirmations to ensure that it won't be reorganized: + - MUST send `announcement_signatures` for the funding transaction. + - After `splice_locked` has been sent and received AND the splice transaction has enough confirmations to ensure that it won't be reorganized: + - MUST send `announcement_signatures` for the matching splice transaction. - Otherwise: - MUST NOT send the `announcement_signatures` message. - Upon reconnection (once the above timing requirements have been met): @@ -95,19 +95,23 @@ A node: - MUST send its own `announcement_signatures` message. - If it receives `announcement_signatures` for the funding transaction: - MUST respond with its own `announcement_signatures` message. + - If it has NOT previously received `announcement_signatures` for a splice transaction: + - MUST retransmit `splice_locked` for that splice transaction. + - After receiving the remote `splice_locked`: + - MUST send its `announcement_signatures` message. A recipient node: - - If the `short_channel_id` is NOT correct: - - SHOULD send a `warning` and close the connection, or send an - `error` and fail the channel. + - If the `short_channel_id` doesn't match one of its funding transactions: + - SHOULD send a `warning`. - If the `node_signature` OR the `bitcoin_signature` is NOT correct: - - MAY send a `warning` and close the connection, or send an - `error` and fail the channel. + - MAY send a `warning` and close the connection, or send an `error` and fail the channel. - If it has sent AND received a valid `announcement_signatures` message: - If the funding transaction has at least 6 confirmations: - SHOULD queue the `channel_announcement` message for its peers. - If it has not sent `channel_ready`: - SHOULD defer handling the `announcement_signatures` until after it has sent `channel_ready`. + - If it has not sent `splice_locked` for the transaction matching this `short_channel_id`: + - SHOULD defer handling the `announcement_signatures` until after it has sent `splice_locked`. ### Rationale @@ -115,6 +119,12 @@ Channels must not be announced before the funding transaction has enough confirmations, because a blockchain reorganization would otherwise invalidate the `short_channel_id`. +When splicing is used, a `channel_announcement` is generated for every splice +transaction once both sides have sent `splice_locked`. This lets the network +know that the transaction spending a currently active channel is a splice and +not a closing transaction, and this channel can still be used with its updated +`short_channel_id`. + ## The `channel_announcement` Message This gossip message contains ownership information regarding a channel. It ties @@ -164,9 +174,18 @@ The origin node: that the channel was opened within: - for the _Bitcoin blockchain_: - MUST set `chain_hash` value (encoded in hex) equal to `6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000`. - - MUST set `short_channel_id` to refer to the confirmed funding transaction, - as specified in [BOLT #2](02-peer-protocol.md#the-channel_ready-message). - - Note: the corresponding output MUST be a P2WSH, as described in [BOLT #3](03-transactions.md#funding-transaction-output). + - When announcing a channel creation: + - MUST set `short_channel_id` to refer to the confirmed funding transaction, + as specified in [BOLT #2](02-peer-protocol.md#the-channel_ready-message). + - When announcing a splice transaction: + - MUST set `short_channel_id` to refer to the confirmed splice transaction + for which `splice_locked` has been sent and received, as specified in + [BOLT #2](02-peer-protocol.md#the-splice_locked-message). + - SHOULD keep relaying payments that use the `short_channel_id`s of its + previous `channel_announcement`s. + - SHOULD send a new `channel_update` using the `short_channel_id` that + matches the latest `channel_announcement`. + - Note: the corresponding output MUST be a P2WSH, as described in [BOLT #3](03-transactions.md#funding-transaction-output). - MUST set `node_id_1` and `node_id_2` to the public keys of the two nodes operating the channel, such that `node_id_1` is the lexicographically-lesser of the two compressed keys sorted in ascending lexicographic order. @@ -254,9 +273,11 @@ optional) features will have _odd_ feature bits, while incompatible features will have _even_ feature bits (["It's OK to be odd!"](00-introduction.md#glossary-and-terminology-guide)). -A delay of 12 blocks is used when forgetting a channel on funding output spend -as to permit a new `channel_announcement` to propagate which indicates this -channel was spliced. +A delay of 12 blocks is used when forgetting a channel after detecting that it +has been spent: this can allow a new `channel_announcement` to propagate to +indicate that this channel was spliced and not closed. Thanks to this delay, +payments can still be relayed on the channel while the splice transaction is +waiting for enough confirmations. ## The `node_announcement` Message From 1d5e0ce42887f6565efc9969a860cc84faed116e Mon Sep 17 00:00:00 2001 From: t-bast Date: Thu, 27 Mar 2025 10:32:21 +0100 Subject: [PATCH 04/21] Clarify that `batch` can only be used when greater than 1 The `batch` TLV field is only included when there is actually a batch of messages, which means it is strictly greater than 1. --- 02-peer-protocol.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/02-peer-protocol.md b/02-peer-protocol.md index 0a408d7ee..12960d4a5 100644 --- a/02-peer-protocol.md +++ b/02-peer-protocol.md @@ -2940,7 +2940,7 @@ fee changes). to the ordering of the commitment transaction (see [BOLT #3](03-transactions.md#transaction-input-and-output-ordering)). - if it has not recently received a message from the remote node: - SHOULD use `ping` and await the reply `pong` before sending `commitment_signed`. - - If there are `N` pending splice transactions: + - If there are `N` pending splice transactions (with `N` greater than `0`): - MUST send `commitment_signed` for the current channel funding output. - MUST send `commitment_signed` for each of the splice transactions. - MUST set `batch_size` to `N + 1` in every `commitment_signed` message. @@ -2963,6 +2963,8 @@ A receiving node: - If there are pending splice transactions and `batch` is not set: - MUST send an `error` and fail the channel. - If `batch` is set: + - If `batch` is smaller than or equal to `1`: + - MUST send an `error` and fail the channel. - MUST wait until it has received `batch_size` messages. - If there are pending splice transactions: - MUST validate each `commitment_signed` based on `funding_txid`. From 29ca2a8cb9a9538268d4f0d3ecb916691cfca412 Mon Sep 17 00:00:00 2001 From: t-bast Date: Thu, 27 Mar 2025 10:33:45 +0100 Subject: [PATCH 05/21] Clarify 0-conf support We clarify the 0-conf support by disallowing sending `splice_init` before `splice_locked` has been exchanged. When using 0-conf, peers should send `splice_locked` immediately after exchanging signatures, which allows creating another splice afterwards. --- 02-peer-protocol.md | 43 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/02-peer-protocol.md b/02-peer-protocol.md index 12960d4a5..1fcef73d5 100644 --- a/02-peer-protocol.md +++ b/02-peer-protocol.md @@ -1633,6 +1633,7 @@ The sending node: - MUST NOT send `splice_init` if another splice has been negotiated but `splice_locked` has not been sent and received. - MUST NOT send `splice_init` if it has previously sent `shutdown`. + - MUST set `funding_feerate_perkw` to the feerate for the splice transaction. - If it is splicing funds out of the channel: - MUST set `funding_contribution_satoshis` to a negative value matching the amount that will be subtracted from its current channel balance. @@ -1660,6 +1661,8 @@ The receiving node: - If it has received `shutdown`: - MUST send a `warning` and close the connection or send an `error` and fail the channel. + - If the `funding_feerate_perkw` is unacceptable: + - MUST respond with `tx_abort`. - If `funding_contribution_satoshis` is negative and its absolute value is greater than the sending node's current channel balance: - MUST send a `warning` and close the connection or send an `error` @@ -1862,6 +1865,7 @@ The sending node: - MUST NOT send `tx_init_rbf` if it is not the quiescence initiator. - MAY send `tx_init_rbf` even if it is not the splice initiator. - MUST NOT send `tx_init_rbf` if it has previously sent `splice_locked`. + - MUST NOT send `tx_init_rbf` if `option_zeroconf` has been negotiated. - MAY set `funding_output_contribution` to a different value than the `funding_contribution_satoshis` used in `splice_init` or `splice_ack`, or in previous RBF attempts. @@ -1876,6 +1880,9 @@ The receiving node: - If the sender previously sent `splice_locked`: - MUST send a `warning` and close the connection or send an `error` and fail the channel. + - If `option_zeroconf` has been negotiated: + - MUST send a `warning` and close the connection or send an `error` + and fail the channel. - If `funding_output_contribution` is negative and its absolute value is greater than the sending node's current channel balance: - MUST send a `warning` and close the connection or send an `error` @@ -1889,7 +1896,9 @@ this opportunity to splice additional funds into or out of the channel without waiting for the initial splice transaction to confirm. Since splice transactions always spend the current channel funding output, the -RBF attempts automatically double-spend each other. +RBF attempts automatically double-spend each other. We thus disallow RBF when +`option_zeroconf` has been negotiated, because that creates a risk of losing +funds. #### The `tx_ack_rbf` Message @@ -1946,14 +1955,34 @@ the locked transaction replaces the previous funding transaction. Each node: - If any splice transaction reaches acceptable depth: - MUST send `splice_locked` with the `txid` of that transaction. + - If `option_zeroconf` has been negotiated: + - SHOULD send `splice_locked` immediately after exchanging `tx_signatures`. + +The receiving node: + - If `splice_txid` doesn't match any of its pending splice transactions: + - MUST send a `warning` and close the connection, or send an `error` and + fail the channel. Once a node has sent and received `splice_locked`: - - MUST consider the locked splice transaction to be the new funding - transaction for all future `commitment_signed` messages and splice - negotiations. - - SHOULD discard the previous funding transaction and RBF attempts. - - MUST send `announcement_signatures` with `short_channel_id` matching - the locked splice transaction. + - If the `splice_txid`s match: + - MUST stop sending `commitment_signed` for RBF attempts and ancestors + of this splice transaction. + - MAY discard RBF attempts and ancestor transactions. + - If `announce_channel` is set for this channel: + - MUST send `announcement_signatures` with `short_channel_id` matching + this splice transaction. + - If the `splice_txid`s are for different RBF candidates: + - SHOULD ignore the message. + - MAY send an `error` and fail the channel. + +##### Rationale + +If nodes are on a different fork of the blockchain, they may disagree on which +RBF attempt has been confirmed: in that case nodes can either close the channel +or simply ignore `splice_locked` and wait for one of the forks to eventually +replace the other, at which point both nodes should agree on which RBF attempt +confirmed and exchange `splice_locked` for the same `splice_txid` to complete +the splice. ## Channel Close From e587360a2ed730c59fe2fefc7a23538853f3029e Mon Sep 17 00:00:00 2001 From: t-bast Date: Thu, 15 May 2025 08:58:57 +0200 Subject: [PATCH 06/21] Clarify `splice_locked` retransmission As proposed by @ddustin. --- 02-peer-protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/02-peer-protocol.md b/02-peer-protocol.md index 1fcef73d5..7343c3f1a 100644 --- a/02-peer-protocol.md +++ b/02-peer-protocol.md @@ -3374,7 +3374,7 @@ A receiving node: - 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 + - if `your_last_funding_locked` is set and it does not match the most recent `splice_locked` it has sent: - MUST retransmit `splice_locked`. From 765a80e41dbf477a22f113973e6e838ba5e644a1 Mon Sep 17 00:00:00 2001 From: t-bast Date: Thu, 15 May 2025 10:23:40 +0200 Subject: [PATCH 07/21] Introduce a `start_batch` message for `commit_sig` Instead of repeating a `batch` TLV in each `commit_sig` message when there are pending splice transactions, we prefix the batch of `commit_sig` messages with a `start_batch` message. The following `commit_sig` messages must be sent immediately afterwards, without inserting any other unrelated message in that batch. This allows the receiver to group the `commit_sig` messages when reading them from the wire and process them together. This mechanism is more general and may be reused for other scenarios in the future. --- 02-peer-protocol.md | 94 +++++++++++++++++++++++++++++++++-------- bolt02/splicing-test.md | 54 ++++++++++++++--------- 2 files changed, 111 insertions(+), 37 deletions(-) diff --git a/02-peer-protocol.md b/02-peer-protocol.md index 7343c3f1a..ace862586 100644 --- a/02-peer-protocol.md +++ b/02-peer-protocol.md @@ -47,6 +47,7 @@ operation, and closing. * [`cltv_expiry_delta` Selection](#cltv_expiry_delta-selection) * [Adding an HTLC: `update_add_htlc`](#adding-an-htlc-update_add_htlc) * [Removing an HTLC: `update_fulfill_htlc`, `update_fail_htlc`, and `update_fail_malformed_htlc`](#removing-an-htlc-update_fulfill_htlc-update_fail_htlc-and-update_fail_malformed_htlc) + * [Batching channel messages](#batching-channel-messages) * [Committing Updates So Far: `commitment_signed`](#committing-updates-so-far-commitment_signed) * [Completing the Transition to the Updated State: `revoke_and_ack`](#completing-the-transition-to-the-updated-state-revoke_and_ack) * [Updating Fees: `update_fee`](#updating-fees-update_fee) @@ -1786,7 +1787,7 @@ adds a massive amount to the channel, then you only have to add more reserve if you want to contribute to the splice (and you can use `tx_remove_output` and/or `tx_remove_input` part-way through if this happens). -### The `commitment_signed` Message +#### The `commitment_signed` Message After exchanging `tx_complete`, both peers send `commitment_signed` to commit to the splice transaction by creating a commitment transaction spending the @@ -1795,7 +1796,7 @@ new channel funding output. The usual [`commitment_signed`](#committing-updates-so-far-commitment_signed) requirements apply with the following additions. -#### Requirements +##### Requirements The sending node: - MUST create a commitment transaction that spends the splice funding output and: @@ -1803,7 +1804,7 @@ The sending node: to the main balance of their respective sender. - Uses the same feerate as the existing commitment transaction. - Uses the same `commitment_number` as the existing commitment transaction. - - Does not set the `batch` field. + - Does not set the `funding_txid` TLV field. - MUST send signatures for pending HTLCs. - MUST remember the details of this splice transaction. @@ -1818,7 +1819,7 @@ On reconnection: - If `next_funding_txid` matches the splice transaction: - MUST retransmit `commitment_signed`. -#### Rationale +##### Rationale Once peers are ready to exchange commitment signatures, they must remember the details of the splice transaction to allow resuming the signatures @@ -1864,6 +1865,8 @@ The sending node: - MUST NOT send `tx_init_rbf` if the channel is not quiescent. - MUST NOT send `tx_init_rbf` if it is not the quiescence initiator. - MAY send `tx_init_rbf` even if it is not the splice initiator. + - If there are more than 10 pending RBF attempts: + - MUST set a high enough `feerate` to ensure quick confirmation. - MUST NOT send `tx_init_rbf` if it has previously sent `splice_locked`. - MUST NOT send `tx_init_rbf` if `option_zeroconf` has been negotiated. - MAY set `funding_output_contribution` to a different value than the @@ -1877,6 +1880,12 @@ The receiving node: - If the sending node is not the quiescence initiator: - MUST send a `warning` and close the connection or send an `error` and fail the channel. + - If another RBF attempt has been created recently: + - SHOULD send `tx_abort` to reject this RBF attempt and wait for the + previous RBF attempt to confirm. + - If there are more than 10 pending RBF attempts and the `feerate` is not + high enough to ensure quick confirmation: + - SHOULD send `tx_abort` to reject the RBF attempt. - If the sender previously sent `splice_locked`: - MUST send a `warning` and close the connection or send an `error` and fail the channel. @@ -1895,6 +1904,12 @@ We allow both nodes to initiate RBF, because any one of them may want to take this opportunity to splice additional funds into or out of the channel without waiting for the initial splice transaction to confirm. +We limit the number of pending RBF attempts, otherwise we may reach the +`batch_size` limit defined in [`start_batch`](#batching-channel-messages). +We require using a large enough feerate when we've already created many RBF +attempts, and we wait between RBF attempts to allow a previous attempt to +confirm. + Since splice transactions always spend the current channel funding output, the RBF attempts automatically double-spend each other. We thus disallow RBF when `option_zeroconf` has been negotiated, because that creates a risk of losing @@ -2933,6 +2948,44 @@ such detection is left as an option. Nodes inside a blinded route must use `invalid_onion_blinding` to avoid leaking information to senders trying to probe the blinded route. +### Batching channel messages + +Multiple channel messages can be grouped together as a single logical message +using the `start_batch` message. + +1. type: 127 (`start_batch`) +2. data: + * [`channel_id`:`channel_id`] + * [`u16`:`batch_size`] + +#### Requirements + +A sending node: + - MUST set `batch_size` to a value strictly greater than 1. + - MUST set `batch_size` to a value strictly lower than 20. + - After sending `start_batch`: + - MUST send `batch_size` messages for the same `channel_id`, without any + other unrelated messages in-between. + +A receiving node: + - If `batch_size` is not strictly greater than 1: + - MUST ignore the `start_batch` message. + - SHOULD send a `warning`. + - If `batch_size` is greater than or equal to 20: + - MUST send a `warning` and close the connection, or send an `error` and + fail the channel. + - MUST group the next `batch_size` messages and process them together. + - If one of those messages is not for the specified `channel_id`: + - MUST send a `warning` and close the connection, or send an `error` and + fail the channel. + +#### Rationale + +We limit the `batch_size` to 20 elements to protect against excessive queuing +that could be abused to DoS receiving nodes. The `start_batch` message is only +used for splice RBF attempts so far: 20 RBF attempts should be enough to get +transactions confirmed. + ### Committing Updates So Far: `commitment_signed` When a node has changes for the remote commitment, it can apply them, @@ -2949,9 +3002,8 @@ sign the resulting transaction (as defined in [BOLT #3](03-transactions.md)), an 1. `tlv_stream`: `commitment_signed_tlvs` 2. types: - 1. type: 0 (`batch`) + 1. type: 0 (`funding_txid`) 2. data: - * [`u16`:`batch_size`] * [`sha256`:`funding_txid`] #### Requirements @@ -2970,12 +3022,15 @@ fee changes). - if it has not recently received a message from the remote node: - SHOULD use `ping` and await the reply `pong` before sending `commitment_signed`. - If there are `N` pending splice transactions (with `N` greater than `0`): + - MUST send `start_batch` first with `batch_size` set to `N + 1`. - MUST send `commitment_signed` for the current channel funding output. - MUST send `commitment_signed` for each of the splice transactions. - - MUST set `batch_size` to `N + 1` in every `commitment_signed` message. - - MUST set `funding_txid` to the funding transaction spent by that commitment. + - MUST set `funding_txid` in each `commitment_signed` message to match the + funding transaction spent by that commitment. + - MUST NOT send any other message before it has sent the batch of + `commitment_signed` messages. - Otherwise: - - MUST NOT include the `batch` field. + - MUST NOT include the `funding_txid` field. A receiving node: - once all pending updates are applied: @@ -2989,12 +3044,13 @@ A receiving node: - if any `htlc_signature` is not valid for the corresponding HTLC transaction OR non-compliant with LOW-S-standard rule [LOWS](https://github.com/bitcoin/bitcoin/pull/6769): - MUST send a `warning` and close the connection, or send an `error` and fail the channel. - - If there are pending splice transactions and `batch` is not set: + - If there are pending splice transactions and the sending node did not + send `start_batch` followed by a batch of `commitment_signed` messages: - MUST send an `error` and fail the channel. - - If `batch` is set: - - If `batch` is smaller than or equal to `1`: + - If the sending node sent `start_batch` and we are processing a batch of + `commitment_signed` messages: + - If `funding_txid` is missing in one of the `commitment_signed` messages: - MUST send an `error` and fail the channel. - - MUST wait until it has received `batch_size` messages. - If there are pending splice transactions: - MUST validate each `commitment_signed` based on `funding_txid`. - If `commitment_signed` is missing for a funding transaction: @@ -3034,10 +3090,12 @@ attaching other inputs and outputs, hence the modified signature flags. Splicing requires us to send and receive additional signatures, as we don't know which (if any) of the splice transactions will end up being the new -channel funding transaction. We send `commitment_signed` for each of the -pending splice transactions and for the current funding transaction. When -sending `splice_locked`, we may receive obsolete `commitment_signed` from -our peer: we can safely ignore them by filtering on `funding_txid`. +channel funding transaction. We use `start_batch` to send a batch of +`commitment_signed` messages containing signatures for each of the pending +splice transactions and for the current funding transaction. After sending +`splice_locked`, we may receive a batch containing obsolete `commitment_signed` +messages from our peer that they started sending before receiving our +`splice_locked`: we can safely ignore them by filtering on `funding_txid`. ### Completing the Transition to the Updated State: `revoke_and_ack` @@ -3067,7 +3125,7 @@ A sending node: - MUST set `next_per_commitment_point` to the values for its next commitment transaction. - MUST send a single `revoke_and_ack` message, even if it is responding to - a `batch` of `commitment_signed` messages. + a batch of `commitment_signed` messages. A receiving node: - if `per_commitment_secret` is not a valid secret key or does not generate the previous `per_commitment_point`: diff --git a/bolt02/splicing-test.md b/bolt02/splicing-test.md index 2db574839..6f9f6376c 100644 --- a/bolt02/splicing-test.md +++ b/bolt02/splicing-test.md @@ -104,15 +104,19 @@ Alice initiates a splice: |----------------------------->| | update_add_htlc | |----------------------------->| - | commit_sig | batch_size = 2, funding_txid = FundingTx1, commitment_number = 11 + | start_batch | batch_size = 2 |----------------------------->| - | commit_sig | batch_size = 2, funding_txid = FundingTx2, commitment_number = 11 + | commit_sig | funding_txid = FundingTx1, commitment_number = 11 + |----------------------------->| + | commit_sig | funding_txid = FundingTx2, commitment_number = 11 |----------------------------->| | revoke_and_ack | |<-----------------------------| - | commit_sig | batch_size = 2, funding_txid = FundingTx1, commitment_number = 11 + | start_batch | batch_size = 2 + |<-----------------------------| + | commit_sig | funding_txid = FundingTx1, commitment_number = 11 |<-----------------------------| - | commit_sig | batch_size = 2, funding_txid = FundingTx2, commitment_number = 11 + | commit_sig | funding_txid = FundingTx2, commitment_number = 11 |<-----------------------------| | revoke_and_ack | |----------------------------->| @@ -239,19 +243,23 @@ Alice initiates a splice: |----------------------------->| | update_add_htlc | |----------------------------->| - | commit_sig | batch_size = 3, funding_txid = FundingTx1, commitment_number = 11 + | start_batch | batch_size = 3 |----------------------------->| - | commit_sig | batch_size = 3, funding_txid = FundingTx2a, commitment_number = 11 + | commit_sig | funding_txid = FundingTx1, commitment_number = 11 |----------------------------->| - | commit_sig | batch_size = 3, funding_txid = FundingTx2b, commitment_number = 11 + | commit_sig | funding_txid = FundingTx2a, commitment_number = 11 + |----------------------------->| + | commit_sig | funding_txid = FundingTx2b, commitment_number = 11 |----------------------------->| | revoke_and_ack | |<-----------------------------| - | commit_sig | batch_size = 3, funding_txid = FundingTx1, commitment_number = 11 + | start_batch | batch_size = 3 + |<-----------------------------| + | commit_sig | funding_txid = FundingTx1, commitment_number = 11 |<-----------------------------| - | commit_sig | batch_size = 3, funding_txid = FundingTx2a, commitment_number = 11 + | commit_sig | funding_txid = FundingTx2a, commitment_number = 11 |<-----------------------------| - | commit_sig | batch_size = 3, funding_txid = FundingTx2b, commitment_number = 11 + | commit_sig | funding_txid = FundingTx2b, commitment_number = 11 |<-----------------------------| | revoke_and_ack | |----------------------------->| @@ -270,13 +278,15 @@ Alice initiates a splice: |----------------------------->| | 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 + | start_batch | batch_size = 3 + |----------------------------->| + | commit_sig | 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 | 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 + | commit_sig | funding_txid = FundingTx2b, commitment_number = 12 -> this message can be ignored by Bob since FundingTx2a has been locked |----------------------------->| | revoke_and_ack | |<-----------------------------| @@ -585,9 +595,11 @@ Alice initiates a splice, but disconnects before Bob receives her tx_signatures |----------------------X | | update_add_htlc | |----------------------X | - | commit_sig | batch_size = 2, funding_txid = FundingTx1, commitment_number = 11 + | start_batch | batch_size = 2 + |----------------------X | + | commit_sig | funding_txid = FundingTx1, commitment_number = 11 |----------------------X | - | commit_sig | batch_size = 2, funding_txid = FundingTx2, commitment_number = 11 + | commit_sig | funding_txid = FundingTx2, commitment_number = 11 |----------------------X | | | Active commitments: | | @@ -604,15 +616,19 @@ Alice initiates a splice, but disconnects before Bob receives her tx_signatures |----------------------------->| | update_add_htlc | |----------------------------->| - | commit_sig | batch_size = 2, funding_txid = FundingTx1, commitment_number = 11 + | start_batch | batch_size = 2 |----------------------------->| - | commit_sig | batch_size = 2, funding_txid = FundingTx2, commitment_number = 11 + | commit_sig | funding_txid = FundingTx1, commitment_number = 11 + |----------------------------->| + | commit_sig | funding_txid = FundingTx2, commitment_number = 11 |----------------------------->| | revoke_and_ack | |<-----------------------------| - | commit_sig | batch_size = 2, funding_txid = FundingTx1, commitment_number = 11 + | start_batch | batch_size = 2 + |<-----------------------------| + | commit_sig | funding_txid = FundingTx1, commitment_number = 11 |<-----------------------------| - | commit_sig | batch_size = 2, funding_txid = FundingTx2, commitment_number = 11 + | commit_sig | funding_txid = FundingTx2, commitment_number = 11 |<-----------------------------| | revoke_and_ack | |----------------------------->| From 2822a528491b5bb72336e2c450028beb0a380552 Mon Sep 17 00:00:00 2001 From: Richard Myers Date: Mon, 2 Jun 2025 16:54:42 +0200 Subject: [PATCH 08/21] Add more reestablish test vectors to splicing-test.md (#5) We add more test vectors to describe how reconnection should be handled after a splice, before receiving `splice_locked`, when there are pending updates. --- bolt02/splicing-test.md | 270 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 268 insertions(+), 2 deletions(-) diff --git a/bolt02/splicing-test.md b/bolt02/splicing-test.md index 6f9f6376c..9ee99b00c 100644 --- a/bolt02/splicing-test.md +++ b/bolt02/splicing-test.md @@ -15,7 +15,9 @@ We detail the exact flow of messages for each scenario, and highlight several ed * [Disconnection with both sides sending `tx_signatures`](#disconnection-with-both-sides-sending-tx_signatures) * [Disconnection with both sides sending `tx_signatures` and channel updates](#disconnection-with-both-sides-sending-tx_signatures-and-channel-updates) * [Disconnection with concurrent `splice_locked`](#disconnection-with-concurrent-splice_locked) - + * [Disconnection after exchanging `tx_signatures` and one side sends `commit_sig` for channel update](#disconnection-after-exchanging-tx_signatures-and-one-side-sends-commit_sig-for-channel-update) + * [Disconnection after exchanging `tx_signatures` and both sides send `commit_sig` for channel update; `revoke_and_ack` not received](#disconnection-after-exchanging-tx_signatures-and-both-sides-send-commit_sig-for-channel-update-revoke_and_ack-not-received) + * [Disconnection after exchanging `tx_signatures` and both sides send `commit_sig` for channel update](#disconnection-after-exchanging-tx_signatures-and-both-sides-send-commit_sig-for-channel-update) ## 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. @@ -53,9 +55,15 @@ The only `interactive-tx` messages we explicitly list are the consecutive `tx_co We also assume that both peers use the same `commitment_number` for simplicity. +Comprehensive testing should include: +* Splice-in, Splice-out and their sequential combinations. +* Peers that have sufficient balance after the initial funding transaction. +* Peers that have sufficient balance via htlcs. +* Peers with different `commitment_number`. + ### Successful single splice -Let's warm up with the simplest possible flow: a splice transaction that confirms without any disconnection. +Let's warm up with the simplest possible flow: a splice transaction that confirms without any disconnection. This flow should be tested for different scenarios, such as: ```text Initial active commitments: @@ -724,3 +732,261 @@ Alice initiates a splice, but disconnections happen when exchanging splice_locke | revoke_and_ack | |----------------------------->| ``` + +### Disconnection after exchanging `tx_signatures` and one side sends `commit_sig` for channel update + +In this scenario, a disconnection happens when both sides have sent and received `tx_signatures`. +The second signer also sent a new signature for additional changes to apply after their `tx_signatures`. +They are able to resume the signature exchange on reconnection and continue with the `commit_sig` exchange. + +```text +Initial active commitments: + + commitment_number = 10 + +------------+ + | FundingTx1 | + +------------+ + +Alice initiates a splice, but disconnects before Bob receives her commit_sigs for new updates: + + Alice Bob + | stfu | + |----------------------------->| + | stfu | + |<-----------------------------| + | splice_init | + |----------------------------->| + | splice_ack | + |<-----------------------------| + | | + | | + |<---------------------------->| + | | + | tx_complete | + |----------------------------->| + | tx_complete | + |<-----------------------------| + | commit_sig | + |----------------------------->| + | commit_sig | + |<-----------------------------| + | tx_signatures | + |<-----------------------------| + | tx_signatures | + |----------------------------->| + | update_add_htlc | + |----------------------------->| + | start_batch | batch_size = 2 + |----------------------X | + | commit_sig | funding_txid = FundingTx1, commitment_number = 11 + |----------------------X | + | commit_sig | 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 = null, next_commitment_number = 11, next_revocation_number = 10 + |<-----------------------------| + | update_add_htlc | + |----------------------------->| + | start_batch | batch_size = 2 + |----------------------------->| + | commit_sig | funding_txid = FundingTx1, commitment_number = 11 + |----------------------------->| + | commit_sig | funding_txid = FundingTx2, commitment_number = 11 + |----------------------------->| + | revoke_and_ack | + |<-----------------------------| + | start_batch | batch_size = 2 + |<-----------------------------| + | commit_sig | funding_txid = FundingTx1, commitment_number = 11 + |<-----------------------------| + | commit_sig | funding_txid = FundingTx2, commitment_number = 11 + |<-----------------------------| + | revoke_and_ack | + |----------------------------->| + | | Active commitments: + | | + | | commitment_number = 11 + | | +------------+ +------------+ + | | | FundingTx1 |------->| FundingTx2 | + | | +------------+ +------------+ +``` + +### Disconnection after exchanging `tx_signatures` and both sides send `commit_sig` for channel update; `revoke_and_ack` not received + +In this scenario, a disconnection happens when both sides have sent and received `tx_signatures`. +Both signers also send new signatures for additional changes to apply after their `tx_signatures`. +The first signer has **not** received the second signer's `revoke_and_ack` message for the channel update. +They are able to resume the signature exchange on reconnection. + +```text +Initial active commitments: + + commitment_number = 10 + +------------+ + | FundingTx1 | + +------------+ + +Alice initiates a splice, but disconnects before Bob receives her revoke_and_ack and commit_sigs for new updates: + + Alice Bob + | stfu | + |----------------------------->| + | stfu | + |<-----------------------------| + | splice_init | + |----------------------------->| + | splice_ack | + |<-----------------------------| + | | + | | + |<---------------------------->| + | | + | tx_complete | + |----------------------------->| + | tx_complete | + |<-----------------------------| + | commit_sig | + |----------------------------->| + | commit_sig | + |<-----------------------------| + | tx_signatures | + |<-----------------------------| + | tx_signatures | + |----------------------------->| + | update_add_htlc | + |----------------------------->| + | start_batch | batch_size = 2 + |----------------------------->| + | commit_sig | funding_txid = FundingTx1, commitment_number = 11 + |----------------------------->| + | commit_sig | funding_txid = FundingTx2, commitment_number = 11 + |----------------------------->| + | revoke_and_ack | + | X----------------------| + | start_batch | batch_size = 2 + | X----------------------| + | commit_sig | funding_txid = FundingTx1, commitment_number = 11 + | X----------------------| + | commit_sig | 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 = null, next_commitment_number = 11, next_revocation_number = 10 + |<-----------------------------| + | revoke_and_ack | + |<-----------------------------| + | start_batch | batch_size = 2 + |<-----------------------------| + | commit_sig | funding_txid = FundingTx1, commitment_number = 11 + |<-----------------------------| + | commit_sig | funding_txid = FundingTx2, commitment_number = 11 + |<-----------------------------| + | revoke_and_ack | + |----------------------------->| + | | Active commitments: + | | + | | commitment_number = 11 + | | +------------+ +------------+ + | | | FundingTx1 |------->| FundingTx2 | + | | +------------+ +------------+ +``` + +### Disconnection after exchanging `tx_signatures` and both sides send `commit_sig` for channel update + +In this scenario, a disconnection happens when both sides have sent and received `tx_signatures`. +Both signers also send new signatures for additional changes to apply after their `tx_signatures`. +The first signer has received the second signer's `revoke_and_ack` message for the channel update. +They are able to resume the signature exchange on reconnection. + +```text +Initial active commitments: + + commitment_number = 10 + +------------+ + | FundingTx1 | + +------------+ + +Alice initiates a splice, but disconnects before she receives Bob's commit_sigs for new updates: + + Alice Bob + | stfu | + |----------------------------->| + | stfu | + |<-----------------------------| + | splice_init | + |----------------------------->| + | splice_ack | + |<-----------------------------| + | | + | | + |<---------------------------->| + | | + | tx_complete | + |----------------------------->| + | tx_complete | + |<-----------------------------| + | commit_sig | + |----------------------------->| + | commit_sig | + |<-----------------------------| + | tx_signatures | + |<-----------------------------| + | tx_signatures | + |----------------------------->| + | update_add_htlc | + |----------------------------->| + | start_batch | batch_size = 2 + |----------------------------->| + | commit_sig | funding_txid = FundingTx1, commitment_number = 11 + |----------------------------->| + | commit_sig | funding_txid = FundingTx2, commitment_number = 11 + |----------------------------->| + | revoke_and_ack | + |<-----------------------------| + | start_batch | batch_size = 2 + | X----------------------| + | commit_sig | funding_txid = FundingTx1, commitment_number = 11 + | X----------------------| + | commit_sig | 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 = 11 + |----------------------------->| + | channel_reestablish | next_funding_txid = null, next_commitment_number = 11, next_revocation_number = 10 + |<-----------------------------| + | start_batch | batch_size = 2 + |<-----------------------------| + | commit_sig | funding_txid = FundingTx1, commitment_number = 11 + |<-----------------------------| + | commit_sig | funding_txid = FundingTx2, commitment_number = 11 + |<-----------------------------| + | revoke_and_ack | + |----------------------------->| + | | Active commitments: + | | + | | commitment_number = 11 + | | +------------+ +------------+ + | | | FundingTx1 |------->| FundingTx2 | + | | +------------+ +------------+ +``` From b36ab0cdd091f899e0c0319686faabb27033d82b Mon Sep 17 00:00:00 2001 From: t-bast Date: Wed, 11 Jun 2025 16:43:25 +0200 Subject: [PATCH 09/21] Always set `commit_sig.funding_txid` It is always helpful to reference the `funding_txid` that is spent by a `commit_sig`, even when there are no pending splices. It's also easier for implementation to always include it. --- 02-peer-protocol.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/02-peer-protocol.md b/02-peer-protocol.md index ace862586..bffd03352 100644 --- a/02-peer-protocol.md +++ b/02-peer-protocol.md @@ -1804,7 +1804,6 @@ The sending node: to the main balance of their respective sender. - Uses the same feerate as the existing commitment transaction. - Uses the same `commitment_number` as the existing commitment transaction. - - Does not set the `funding_txid` TLV field. - MUST send signatures for pending HTLCs. - MUST remember the details of this splice transaction. @@ -3002,7 +3001,7 @@ sign the resulting transaction (as defined in [BOLT #3](03-transactions.md)), an 1. `tlv_stream`: `commitment_signed_tlvs` 2. types: - 1. type: 0 (`funding_txid`) + 1. type: 1 (`funding_txid`) 2. data: * [`sha256`:`funding_txid`] @@ -3019,6 +3018,8 @@ change the commitment transaction aside from the new revocation number fee changes). - MUST include one `htlc_signature` for every HTLC transaction corresponding to the ordering of the commitment transaction (see [BOLT #3](03-transactions.md#transaction-input-and-output-ordering)). + - MUST set `funding_txid` to the funding transaction spent by this commitment + transaction. - if it has not recently received a message from the remote node: - SHOULD use `ping` and await the reply `pong` before sending `commitment_signed`. - If there are `N` pending splice transactions (with `N` greater than `0`): @@ -3026,11 +3027,9 @@ fee changes). - MUST send `commitment_signed` for the current channel funding output. - MUST send `commitment_signed` for each of the splice transactions. - MUST set `funding_txid` in each `commitment_signed` message to match the - funding transaction spent by that commitment. + funding transaction spent by that commitment transaction. - MUST NOT send any other message before it has sent the batch of `commitment_signed` messages. - - Otherwise: - - MUST NOT include the `funding_txid` field. A receiving node: - once all pending updates are applied: From a821628737143c82f1a48b3680a523b3533d6a9b Mon Sep 17 00:00:00 2001 From: t-bast Date: Wed, 11 Jun 2025 16:55:39 +0200 Subject: [PATCH 10/21] Add `message_type` TLV to `start_batch` We add a `message_type` TLV to `start_batch` that must be used when the batch contains only messages of the same type, which is how it is used for splicing (where we send a batch of `commitment_signed` messages). --- 02-peer-protocol.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/02-peer-protocol.md b/02-peer-protocol.md index bffd03352..69f24c4f1 100644 --- a/02-peer-protocol.md +++ b/02-peer-protocol.md @@ -2957,14 +2957,26 @@ using the `start_batch` message. * [`channel_id`:`channel_id`] * [`u16`:`batch_size`] +1. `tlv_stream`: `start_batch_tlvs` +2. types: + 1. type: 1 (`message_type`) + 2. data: + * [`u16`:`message_type`] + #### Requirements A sending node: - MUST set `batch_size` to a value strictly greater than 1. - MUST set `batch_size` to a value strictly lower than 20. + - If the batch only contains messages of the same type: + - MUST set `message_type` to the corresponding + [Bolt 1 type](./01-messaging.md#lightning-message-format). - After sending `start_batch`: - MUST send `batch_size` messages for the same `channel_id`, without any other unrelated messages in-between. + - If `message_type` is set: + - MUST send `batch_size` messages of the same message type, without any + other unrelated messages in-between. A receiving node: - If `batch_size` is not strictly greater than 1: @@ -2977,6 +2989,10 @@ A receiving node: - If one of those messages is not for the specified `channel_id`: - MUST send a `warning` and close the connection, or send an `error` and fail the channel. + - If `message_type` is set, but one of those messages is not of the specified + message type: + - MUST send a `warning` and close the connection, or send an `error` and + fail the channel. #### Rationale @@ -3023,7 +3039,8 @@ fee changes). - if it has not recently received a message from the remote node: - SHOULD use `ping` and await the reply `pong` before sending `commitment_signed`. - If there are `N` pending splice transactions (with `N` greater than `0`): - - MUST send `start_batch` first with `batch_size` set to `N + 1`. + - MUST send `start_batch` first with `batch_size` set to `N + 1` and + `message_type` set to `132` (`commitment_signed`). - MUST send `commitment_signed` for the current channel funding output. - MUST send `commitment_signed` for each of the splice transactions. - MUST set `funding_txid` in each `commitment_signed` message to match the From e01051368ca237ed7bfae75ed995407f144d9bd4 Mon Sep 17 00:00:00 2001 From: t-bast Date: Wed, 11 Jun 2025 17:22:49 +0200 Subject: [PATCH 11/21] Clarify various requirements As suggested by @jkczyz, we clarify requirements around: - the `shared_funding_txid` field - `start_batch` maximum size and RBF attempts - `channel_reestablish` ordering with `splice_locked` --- 02-peer-protocol.md | 52 +++++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/02-peer-protocol.md b/02-peer-protocol.md index 69f24c4f1..54f502098 100644 --- a/02-peer-protocol.md +++ b/02-peer-protocol.md @@ -231,11 +231,16 @@ The receiving node: - MUST add all received inputs to the transaction - MUST fail the negotiation if: - `sequence` is set to `0xFFFFFFFE` or `0xFFFFFFFF` - - the `prevtx` and `prevtx_vout` are identical to a previously added - (and not removed) input's - - `prevtx` is not a valid transaction - - `prevtx_vout` is greater or equal to the number of outputs on `prevtx` - - the `scriptPubKey` of the `prevtx_vout` output of `prevtx` is not exactly a 1-byte push opcode (for the numeric values `0` to `16`) followed by a data push between 2 and 40 bytes + - if `prevtx_len` is `0`: + - `shared_input_txid` is not set + - `shared_input_txid` and `prevtx_vout` don't match the previous funding output + - a previously added (and not removed) input already exists with `shared_input_txid` set + - if `prevtx_len` is not `0`: + - `prevtx` and `prevtx_vout` are identical to a previously added (and not removed) input + - `prevtx` is not a valid transaction + - `prevtx_vout` is greater or equal to the number of outputs on `prevtx` + - the `scriptPubKey` of the `prevtx_vout` output of `prevtx` is not exactly a 1-byte push + opcode (for the numeric values `0` to `16`) followed by a data push between 2 and 40 bytes - the `serial_id` is already included in the transaction - the `serial_id` has the wrong parity - if has received 4096 `tx_add_input` messages during this negotiation @@ -248,8 +253,10 @@ MAY omit this message. `serial_id` is a randomly chosen number which uniquely identifies this input. Inputs in the constructed transaction MUST be sorted by `serial_id`. -`prevtx` is the serialized transaction that contains the output -this input spends. Used to verify that the input is non-malleable. +`prevtx` is the serialized transaction that contains the output this input +spends, used to verify that the input is non-malleable. It can be ommitted +(`prevtx_len` set to `0`) when both peers already know that the input is +non-malleable (e.g. when it is the previous funding output). `prevtx_vout` is the index of the output being spent. @@ -1724,25 +1731,28 @@ The sending node: - If it is the splice initiator: - MUST add the current channel input to the splice transaction by sending `tx_add_input` with `shared_input_txid` containing the - `txid` of the latest funding transaction. + `txid` of the previous funding transaction. - MUST NOT include `prevtx` for that shared input. + - MUST set `prevtx_vout` to the previous funding output index. - If the receiver set `require_confirmed_inputs` in `splice_init`, `splice_ack`, `tx_init_rbf` or `tx_ack_rbf`: - MUST NOT send a `tx_add_input` that contains an unconfirmed input. The receiving node: - If `shared_input_txid` is set: - - If it doesn't match the `txid` of the latest funding transaction: + - If it doesn't match the `txid` of the previous funding transaction: + - MUST fail the negotiation by sending `tx_abort`. + - If `prevtx_vout` doesn't match the previous funding output index: - MUST fail the negotiation by sending `tx_abort`. ##### Rationale The splice transaction must spend the current channel funding output. The splice initiator is responsible for adding that input to the transaction, -and pay the fees for its weight. Since both peers already have access to -the funding transaction, it is wasteful to transmit it in the `prevtx` -field. It may also exceed 65kB, which makes it impossible to include it -in `tx_add_input` anyway, so we only transmit its `txid`. +and pay the fees for its weight. It would be wasteful to transmit the +previous funding transaction in the `prevtx` field, and wouldn't even +be possible for funding transactions that exceed 65kB, so we only transmit +its `txid` using the `shared_input_txid` field. #### The `tx_add_output` Message @@ -2967,7 +2977,7 @@ using the `start_batch` message. A sending node: - MUST set `batch_size` to a value strictly greater than 1. - - MUST set `batch_size` to a value strictly lower than 20. + - MUST set `batch_size` to a value lower than or equal to 20. - If the batch only contains messages of the same type: - MUST set `message_type` to the corresponding [Bolt 1 type](./01-messaging.md#lightning-message-format). @@ -2982,7 +2992,7 @@ A receiving node: - If `batch_size` is not strictly greater than 1: - MUST ignore the `start_batch` message. - SHOULD send a `warning`. - - If `batch_size` is greater than or equal to 20: + - If `batch_size` is strictly greater than 20: - MUST send a `warning` and close the connection, or send an `error` and fail the channel. - MUST group the next `batch_size` messages and process them together. @@ -2998,8 +3008,8 @@ A receiving node: We limit the `batch_size` to 20 elements to protect against excessive queuing that could be abused to DoS receiving nodes. The `start_batch` message is only -used for splice RBF attempts so far: 20 RBF attempts should be enough to get -transactions confirmed. +used for splice RBF attempts so far, which shouldn't need that many attempts +to get transactions confirmed. ### Committing Updates So Far: `commitment_signed` @@ -3358,7 +3368,7 @@ The sending node: - 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`. + - MUST send `splice_locked` for that transaction after exchanging `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`: @@ -3367,10 +3377,10 @@ The sending node: - MUST NOT set `my_current_funding_locked`. - if `my_current_funding_locked` is included and `announce_channel` is set for this channel: - if it has not received `announcement_signatures` for that transaction: - - MUST retransmit `channel_ready` or `splice_locked` after `channel_reestablish`. - - if it receives `channel_ready` for that transaction after `channel_reestablish`: + - MUST retransmit `channel_ready` or `splice_locked` after exchanging `channel_reestablish`. + - if it receives `channel_ready` for that transaction after exchanging `channel_reestablish`: - MUST retransmit `channel_ready` in response, if not already sent. - - if it receives `splice_locked` for that transaction after `channel_reestablish`: + - if it receives `splice_locked` for that transaction after exchanging `channel_reestablish`: - MUST retransmit `splice_locked` in response, if not already sent. A node: From 81b89a6f8ede1e8a285970fc2473aa7b92e649a2 Mon Sep 17 00:00:00 2001 From: t-bast Date: Tue, 17 Jun 2025 09:18:21 +0200 Subject: [PATCH 12/21] Restrict `start_batch` to splicing use-cases As proposed by @ddustin, we explicitly narrow the requirements for `start_batch` to match our only use-case for it (splicing). We can change that in the future if we use this messages for other features. --- 02-peer-protocol.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/02-peer-protocol.md b/02-peer-protocol.md index 54f502098..2693582f1 100644 --- a/02-peer-protocol.md +++ b/02-peer-protocol.md @@ -2978,15 +2978,11 @@ using the `start_batch` message. A sending node: - MUST set `batch_size` to a value strictly greater than 1. - MUST set `batch_size` to a value lower than or equal to 20. - - If the batch only contains messages of the same type: - - MUST set `message_type` to the corresponding - [Bolt 1 type](./01-messaging.md#lightning-message-format). + - MUST set `message_type` to `132` (`commitment_signed` message type as + defined in [Bolt 1](./01-messaging.md#lightning-message-format)). - After sending `start_batch`: - - MUST send `batch_size` messages for the same `channel_id`, without any - other unrelated messages in-between. - - If `message_type` is set: - - MUST send `batch_size` messages of the same message type, without any - other unrelated messages in-between. + - MUST send `batch_size` `commitment_signed` messages for the same + `channel_id`, without any other unrelated messages in-between. A receiving node: - If `batch_size` is not strictly greater than 1: @@ -2999,13 +2995,17 @@ A receiving node: - If one of those messages is not for the specified `channel_id`: - MUST send a `warning` and close the connection, or send an `error` and fail the channel. - - If `message_type` is set, but one of those messages is not of the specified - message type: - - MUST send a `warning` and close the connection, or send an `error` and - fail the channel. + - If `message_type` is missing or not set to the type for `commitment_signed`: + - MUST ignore the `start_batch` message and process the following messages + sequentially. #### Rationale +The `start_batch` message is only used when sending multiple `commitment_signed` +messages during splicing. We thus narrow the requirements for this specific +scenario: they can be relaxed in the future if we use `start_batch` for other +features. + We limit the `batch_size` to 20 elements to protect against excessive queuing that could be abused to DoS receiving nodes. The `start_batch` message is only used for splice RBF attempts so far, which shouldn't need that many attempts From 04cdc02f549d9e1b5a61a0f4c1bb97d06435be81 Mon Sep 17 00:00:00 2001 From: t-bast Date: Mon, 23 Jun 2025 17:13:12 +0200 Subject: [PATCH 13/21] Clarify `channel_ready` and `splice_locked` retransmit Some of those requirements shouldn't be gated on announcing the channel, and we clarify that we retransmit once per connection. --- 02-peer-protocol.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/02-peer-protocol.md b/02-peer-protocol.md index 2693582f1..6f75ab8c7 100644 --- a/02-peer-protocol.md +++ b/02-peer-protocol.md @@ -3375,13 +3375,14 @@ The sending node: - 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`. - - if `my_current_funding_locked` is included and `announce_channel` is set for this channel: - - if it has not received `announcement_signatures` for that transaction: - - MUST retransmit `channel_ready` or `splice_locked` after exchanging `channel_reestablish`. + - if `my_current_funding_locked` is included: + - if `announce_channel` is set for this channel: + - if it has not received `announcement_signatures` for that transaction: + - MUST retransmit `channel_ready` or `splice_locked` after exchanging `channel_reestablish`. - if it receives `channel_ready` for that transaction after exchanging `channel_reestablish`: - - MUST retransmit `channel_ready` in response, if not already sent. + - MUST retransmit `channel_ready` in response, if not already sent since reconnecting. - if it receives `splice_locked` for that transaction after exchanging `channel_reestablish`: - - MUST retransmit `splice_locked` in response, if not already sent. + - MUST retransmit `splice_locked` in response, if not already sent since reconnecting. A node: - if `next_commitment_number` is 1 in both the `channel_reestablish` it From c0d9c52ebc02dd0eb3957384ce3046c185766c8e Mon Sep 17 00:00:00 2001 From: t-bast Date: Wed, 25 Jun 2025 17:09:28 +0200 Subject: [PATCH 14/21] fixup! Clarify `channel_ready` and `splice_locked` retransmit --- 02-peer-protocol.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/02-peer-protocol.md b/02-peer-protocol.md index 6f75ab8c7..8762a826f 100644 --- a/02-peer-protocol.md +++ b/02-peer-protocol.md @@ -3454,11 +3454,10 @@ A receiving node: this funding transaction. A receiving node: - - if `my_current_funding_locked` does not match the most recent `splice_locked` - it has received: + - if splice transactions are pending and `my_current_funding_locked` matches one of + those splice transactions, for which it hasn't received `splice_locked` yet: - 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`. + for this `txid`. - if `your_last_funding_locked` is set and it does not match the most recent `splice_locked` it has sent: - MUST retransmit `splice_locked`. From f9fd539db6cc6f3e532fdc8cc1ebe8eb1a8fd717 Mon Sep 17 00:00:00 2001 From: t-bast Date: Tue, 12 Aug 2025 17:00:48 +0200 Subject: [PATCH 15/21] Re-work `my_current_funding_locked` reestablish We introduce a `retransmit_flags` field to ask our peer to retransmit `announcement_signatures` if we're expecting them: this way we don't need to retransmit `splice_locked` on reconnection to trigger the exchange of `announcement_signatures`. We don't need to retransmit it to let our peer know that we've seen enough confirmations for the splice either, since `my_current_funding_locked` implies that. This allows us to completely remove retransmission of `splice_locked` on reconnection, and also get rid of the `your_last_funding_locked` TLV, which greatly simplifies the reconnection logic. This will work with taproot channels since we'll simply provide nonces in `channel_reestablish` when we need our peer to send announcement signatures. Note that we use a different TLV type to more easily allow migrating existing users of the previous versions of the spec to the latest one. --- 02-peer-protocol.md | 66 ++++++++++++++++++++--------------------- 07-routing-gossip.md | 6 ++-- bolt02/splicing-test.md | 21 +++++-------- 3 files changed, 43 insertions(+), 50 deletions(-) diff --git a/02-peer-protocol.md b/02-peer-protocol.md index 8762a826f..d05acaa40 100644 --- a/02-peer-protocol.md +++ b/02-peer-protocol.md @@ -3296,12 +3296,17 @@ 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`) + 1. type: 5 (`my_current_funding_locked`) 2. data: * [`sha256`:`my_current_funding_locked_txid`] + * [`byte`:`retransmit_flags`] + +The `retransmit_flags` bitfield is used to let our peer know which messages +we expect them to retransmit after the reconnection: + +| Bit Position | Name | +| ------------- | --------------------------| +| 0 | `announcement_signatures` | `next_commitment_number`: A commitment number is a 48-bit incrementing counter for each commitment transaction; counters @@ -3361,35 +3366,24 @@ The sending node: - 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 exchanging `channel_reestablish`. + - MUST include `my_current_funding_locked` with the txid of the latest such transaction. - otherwise: - - MUST set `my_current_funding_locked` to the txid of the last `splice_locked` it sent. + - MUST include `my_current_funding_locked` with 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. + - MUST include `my_current_funding_locked` with the txid of the channel funding transaction. - otherwise (it has never sent `channel_ready` or `splice_locked`): - - MUST NOT set `my_current_funding_locked`. + - MUST NOT include `my_current_funding_locked`. - if `my_current_funding_locked` is included: - if `announce_channel` is set for this channel: - if it has not received `announcement_signatures` for that transaction: - - MUST retransmit `channel_ready` or `splice_locked` after exchanging `channel_reestablish`. - - if it receives `channel_ready` for that transaction after exchanging `channel_reestablish`: - - MUST retransmit `channel_ready` in response, if not already sent since reconnecting. - - if it receives `splice_locked` for that transaction after exchanging `channel_reestablish`: - - MUST retransmit `splice_locked` in response, if not already sent since reconnecting. + - MUST set the `announcement_signatures` bit to `1` in `retransmit_flags`. + - otherwise: + - MUST set the `announcement_signatures` bit to `0` in `retransmit_flags`. A node: - if `next_commitment_number` is 1 in both the `channel_reestablish` it - sent and received and `option_splice` was NOT negotiated: - - MUST retransmit `channel_ready`. - - if `option_splice` was negotiated and `your_last_funding_locked` is not - set in the `channel_reestablish` it received: + sent and received: - MUST retransmit `channel_ready`. - otherwise: - MUST NOT retransmit `channel_ready`, but MAY send `channel_ready` with @@ -3458,9 +3452,11 @@ A receiving node: those splice transactions, for which it hasn't received `splice_locked` yet: - MUST process `my_current_funding_locked` as if it was receiving `splice_locked` for this `txid`. - - if `your_last_funding_locked` is set and it does not match the most recent - `splice_locked` it has sent: - - MUST retransmit `splice_locked`. + - if `my_current_funding_locked` is included with the `announcement_signatures` bit + set in the `retransmit_flags`: + - if `announce_channel` is set for this channel and the receiving node is ready + to send `announcement_signatures` for the corresponding splice transaction: + - MUST retransmit `announcement_signatures`. A node: - MUST NOT assume that previously-transmitted messages were lost, @@ -3536,14 +3532,16 @@ 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. -`your_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. +`my_current_funding_locked` is equivalent to sending `splice_locked`, but is +handled atomically during `channel_reestablish` (instead of requiring a +retransmission of the `splice_locked` message). This is useful to avoid race +conditions with channel updates (for more details about this race condition, +see [this example](./bolt02/splicing-test.md#disconnection-with-concurrent-splice_locked)). +It handles the case where the `splice_locked` message was lost during the +disconnection, or when a splice transaction reaches acceptable depth while +peers are disconnected. It also allows requesting a retransmission of the +`announcement_signatures` message for the latest splice transaction, in case +it wasn't received before disconnecting. # Authors diff --git a/07-routing-gossip.md b/07-routing-gossip.md index cc41ec248..186326b88 100644 --- a/07-routing-gossip.md +++ b/07-routing-gossip.md @@ -96,9 +96,9 @@ A node: - If it receives `announcement_signatures` for the funding transaction: - MUST respond with its own `announcement_signatures` message. - If it has NOT previously received `announcement_signatures` for a splice transaction: - - MUST retransmit `splice_locked` for that splice transaction. - - After receiving the remote `splice_locked`: - - MUST send its `announcement_signatures` message. + - MUST SET the `announcement_signatures` bit in the `retransmit_flags` of `my_current_funding_locked`. + - If the `announcement_signatures` bit is set in the *remote* `retransmit_flags`: + - MUST retransmit its `announcement_signatures` message. A recipient node: - If the `short_channel_id` doesn't match one of its funding transactions: diff --git a/bolt02/splicing-test.md b/bolt02/splicing-test.md index 9ee99b00c..563a86e07 100644 --- a/bolt02/splicing-test.md +++ b/bolt02/splicing-test.md @@ -651,8 +651,7 @@ 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 if it wasn't previously received. -When `last_funding_locked` is set, this lets nodes immediately lock the latest splice transaction. +We use the `my_current_funding_locked` field to "lock" the latest splice on reconnection. ```text Initial active commitments: @@ -696,22 +695,20 @@ Alice initiates a splice, but disconnections happen when exchanging splice_locke | | | 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, my_current_funding_locked = funding_tx2 |----------------------------->| - | channel_reestablish | next_funding_txid = null, your_last_funding_locked = funding_tx1, my_current_funding_locked = funding_tx1 + | channel_reestablish | next_funding_txid = null, 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, my_current_funding_locked = funding_tx2 |----------------------------->| - | channel_reestablish | next_funding_txid = null, your_last_funding_locked = funding_tx2, my_current_funding_locked = funding_tx2 + | channel_reestablish | next_funding_txid = null, 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. + | | Bob's my_current_funding_locked lets Alice know that Bob has locked funding_tx2 while they were disconnected. + | | She can thus immediately lock it as well even though she hasn't received Bob's splice_locked. | | | | Active commitments: | | @@ -723,8 +720,6 @@ Alice initiates a splice, but disconnections happen when exchanging splice_locke |----------------------------->| | 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 | From f6346ba8f4f10d6de0c6f321f5ca4587a2c1f5ec Mon Sep 17 00:00:00 2001 From: t-bast Date: Tue, 23 Sep 2025 10:24:13 +0200 Subject: [PATCH 16/21] Clarify `tx_signatures` ordering The specification for who sends `tx_signatures` first states that the node that has the lowest satoshis contributed, "as defined by total `tx_add_input` values" must sign first. Since the splice initiator is sending `tx_add_input` for the current channel output, that means all of the channel's previous capacity is attributed to the initiator, instead of taking into account each node's pre-splice channel balance. Since this detail is easy to miss, we add a note in the requirements for `tx_signatures` in the splice section to highlight this subtlety. Suggested by @wpaulino --- 02-peer-protocol.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/02-peer-protocol.md b/02-peer-protocol.md index d05acaa40..0cee4a4dc 100644 --- a/02-peer-protocol.md +++ b/02-peer-protocol.md @@ -1823,6 +1823,10 @@ The receiving node: - MUST send `commitment_signed`. - If it should sign first, as specified in the [`tx_signatures` requirements](#the-tx_signatures-message): - MUST send `tx_signatures`. + - Note that since the initiator sends `tx_add_input` for the shared input + (corresponding to the previous channel output), 100% of the previous channel + capacity is attributed to the initiator when computing who must send + `tx_signatures` first (instead of using each node's previous balance). On reconnection: - If `next_funding_txid` matches the splice transaction: From ea25a7eccdad3d5b079c2376226ae26dfb8a5074 Mon Sep 17 00:00:00 2001 From: t-bast Date: Wed, 24 Sep 2025 11:36:46 +0200 Subject: [PATCH 17/21] Avoid `channel_ready` retransmission after splice If nodes have started a splice, this means they have both sent and received `channel_ready` already: in that case, it's unnecessary to retransmit `channel_ready` on reconnection. Suggested by @wpaulino --- 02-peer-protocol.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/02-peer-protocol.md b/02-peer-protocol.md index 0cee4a4dc..3fb4981cc 100644 --- a/02-peer-protocol.md +++ b/02-peer-protocol.md @@ -3387,7 +3387,8 @@ The sending node: A node: - if `next_commitment_number` is 1 in both the `channel_reestablish` it - sent and received: + sent and received, and none of those `channel_reestablish` messages + contain `my_current_funding_locked` for a splice transaction: - MUST retransmit `channel_ready`. - otherwise: - MUST NOT retransmit `channel_ready`, but MAY send `channel_ready` with @@ -3516,10 +3517,10 @@ commitment number 0 is created during opening. `commitment_signed` for commitment number 1 is send and then the revocation for commitment number 0 is received. -`channel_ready` is implicitly acknowledged by the start of normal -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. +`channel_ready` is implicitly acknowledged by the start of normal 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) or after a +splice transaction has been locked. A node, which has somehow fallen behind (e.g. has been restored from old backup), can detect that it has fallen From f5fcdbfc2dfcae4abd841ac267a6ec7db1c01194 Mon Sep 17 00:00:00 2001 From: t-bast Date: Wed, 1 Oct 2025 13:28:12 +0200 Subject: [PATCH 18/21] fixup! Avoid `channel_ready` retransmission after splice --- 02-peer-protocol.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/02-peer-protocol.md b/02-peer-protocol.md index 3fb4981cc..573c825d5 100644 --- a/02-peer-protocol.md +++ b/02-peer-protocol.md @@ -3388,7 +3388,7 @@ The sending node: A node: - if `next_commitment_number` is 1 in both the `channel_reestablish` it sent and received, and none of those `channel_reestablish` messages - contain `my_current_funding_locked` for a splice transaction: + contain `my_current_funding_locked` or `next_funding` for a splice transaction: - MUST retransmit `channel_ready`. - otherwise: - MUST NOT retransmit `channel_ready`, but MAY send `channel_ready` with @@ -3520,7 +3520,7 @@ the revocation for commitment number 0 is received. `channel_ready` is implicitly acknowledged by the start of normal 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) or after a -splice transaction has been locked. +splice transaction has been created. A node, which has somehow fallen behind (e.g. has been restored from old backup), can detect that it has fallen From 70a2047fe49966cc91762d992ab2547576f8ac3e Mon Sep 17 00:00:00 2001 From: t-bast Date: Fri, 24 Oct 2025 10:33:13 +0200 Subject: [PATCH 19/21] Clarify `my_current_funding_locked` requirements As suggested by @ddustin. --- 02-peer-protocol.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/02-peer-protocol.md b/02-peer-protocol.md index 573c825d5..6d358539b 100644 --- a/02-peer-protocol.md +++ b/02-peer-protocol.md @@ -3372,18 +3372,18 @@ The sending node: - if `option_splice` was negotiated: - if a splice transaction reached acceptable depth while disconnected: - MUST include `my_current_funding_locked` with the txid of the latest such transaction. - - otherwise: + - otherwise, if it has already sent `splice_locked` for any transaction: - MUST include `my_current_funding_locked` with the txid of the last `splice_locked` it sent. - - if it never sent `splice_locked` for any transaction, but it sent `channel_ready`: - - MUST include `my_current_funding_locked` with the txid of the channel funding transaction. - - otherwise (it has never sent `channel_ready` or `splice_locked`): - - MUST NOT include `my_current_funding_locked`. - - if `my_current_funding_locked` is included: - - if `announce_channel` is set for this channel: - - if it has not received `announcement_signatures` for that transaction: - - MUST set the `announcement_signatures` bit to `1` in `retransmit_flags`. - - otherwise: - - MUST set the `announcement_signatures` bit to `0` in `retransmit_flags`. + - otherwise, if it has already sent `channel_ready`: + - MUST include `my_current_funding_locked` with the txid of the channel funding transaction. + - otherwise (it has never sent `channel_ready` or `splice_locked`): + - MUST NOT include `my_current_funding_locked`. + - if `my_current_funding_locked` is included: + - if `announce_channel` is set for this channel: + - if it has not received `announcement_signatures` for that transaction: + - MUST set the `announcement_signatures` bit to `1` in `retransmit_flags`. + - otherwise: + - MUST set the `announcement_signatures` bit to `0` in `retransmit_flags`. A node: - if `next_commitment_number` is 1 in both the `channel_reestablish` it From 5356b3f2ece32e2b197587f9d4de6c8cb58900a6 Mon Sep 17 00:00:00 2001 From: t-bast Date: Mon, 19 Jan 2026 16:40:18 +0100 Subject: [PATCH 20/21] next_funding_txid -> next_funding --- 02-peer-protocol.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/02-peer-protocol.md b/02-peer-protocol.md index 6d358539b..5fb343e09 100644 --- a/02-peer-protocol.md +++ b/02-peer-protocol.md @@ -1829,7 +1829,7 @@ The receiving node: `tx_signatures` first (instead of using each node's previous balance). On reconnection: - - If `next_funding_txid` matches the splice transaction: + - If `next_funding` matches the splice transaction: - MUST retransmit `commitment_signed`. ##### Rationale @@ -1856,7 +1856,7 @@ The receiving node: - MUST consider the channel no longer quiescent. On reconnection: - - If `next_funding_txid` matches the splice transaction: + - If `next_funding` matches the splice transaction: - MUST retransmit `tx_signatures`. ##### Rationale @@ -3364,11 +3364,11 @@ The sending node: - MUST set `your_last_per_commitment_secret` to the last `per_commitment_secret` it received - if it has sent `commitment_signed` for an interactive transaction construction but it has not received `tx_signatures`: - - MUST set `next_funding_txid` to the txid of that interactive transaction. + - MUST set `next_funding` to the txid of that interactive transaction. - if it has not received `commitment_signed` for that interactive transaction: - MUST set `next_commitment_number` to the commitment number of the `commitment_signed` it sent. - otherwise: - - MUST NOT set `next_funding_txid`. + - MUST NOT set `next_funding`. - if `option_splice` was negotiated: - if a splice transaction reached acceptable depth while disconnected: - MUST include `my_current_funding_locked` with the txid of the latest such transaction. @@ -3434,8 +3434,8 @@ A node: - SHOULD send an `error` and fail the channel. A receiving node: - - if `next_funding_txid` is set: - - if `next_funding_txid` matches the latest interactive funding transaction + - if `next_funding` is set: + - if `next_funding` matches the latest interactive funding transaction or the current channel funding transaction: - if `next_commitment_number` is equal to the commitment number of the `commitment_signed` message it sent for this funding transaction: @@ -3445,7 +3445,7 @@ 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 it also sets `next_funding_txid` in its own `channel_reestablish`, but the + - if it also sets `next_funding` in its own `channel_reestablish`, but the values don't match: - MUST send an `error` and fail the channel. - otherwise: @@ -3532,7 +3532,7 @@ transaction to the chain. The other node should wait for that `error` to give the fallen-behind node an opportunity to fix its state first (e.g by restarting with a different backup). -`next_funding_txid` allows peers to finalize the signing steps of an +`next_funding` allows peers to finalize the signing steps of an 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. From e3747936181cc11e0fff4052efe112751f3ddeb9 Mon Sep 17 00:00:00 2001 From: t-bast Date: Mon, 19 Jan 2026 16:46:34 +0100 Subject: [PATCH 21/21] Use `retransmit_flags` from #1289 --- 02-peer-protocol.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/02-peer-protocol.md b/02-peer-protocol.md index 5fb343e09..7b5288542 100644 --- a/02-peer-protocol.md +++ b/02-peer-protocol.md @@ -3366,7 +3366,7 @@ The sending node: it has not received `tx_signatures`: - MUST set `next_funding` to the txid of that interactive transaction. - if it has not received `commitment_signed` for that interactive transaction: - - MUST set `next_commitment_number` to the commitment number of the `commitment_signed` it sent. + - MUST set the `commitment_signed` bit in `next_funding.retransmit_flags`. - otherwise: - MUST NOT set `next_funding`. - if `option_splice` was negotiated: @@ -3437,8 +3437,7 @@ A receiving node: - if `next_funding` is set: - if `next_funding` matches the latest interactive funding transaction or the current channel funding transaction: - - if `next_commitment_number` is equal to the commitment number of the - `commitment_signed` message it sent for this funding transaction: + - if the `commitment_signed` bit is set in `next_funding.retransmit_flags`: - MUST retransmit its `commitment_signed` for that funding transaction. - if it has already received `commitment_signed` and it should sign first, as specified in the [`tx_signatures` requirements](#the-tx_signatures-message):