0-conf Splicing Subtleties

by ADMIN 27 views

Splicing without 0-conf

When we're not using 0-conf, pending splice candidates all double-spend each other, creating a tree of depth 2 where all the leaves are different RBF attempts. Only one of those will eventually confirm, thus invalidating all the other ones.

Before splicing, we only have the latest funding transaction, that can be spent using a commitment transaction:

+------------+        +-----------+
| Funding Tx |------->| Commit Tx |
+------------+        +-----------+

We can start a splice by exchanging splice_init and splice_ack, after which we end up with the following tree:

+------------+        +-----------+
| Funding Tx |---+--->| Commit Tx |
+------------+   |    +-----------+
                 |    +-----------+            +-----------+
                 +--->| Splice Tx |----------->| Commit Tx |
                      +-----------+            +-----------+

At that point, splice_init cannot be sent again until both nodes have sent splice_locked for the splice transaction, indicating that they believe it has reached enough confirmations to not be reorg-ed out.

If that splice transaction doesn't confirm, it can be RBF-ed by exchanging tx_init_rbf and tx_ack_rbf, creating more leaves in the tree:

+------------+        +-----------+
| Funding Tx |---+--->| Commit Tx |
+------------+   |    +-----------+
                 |    +-----------+            +-----------+
                 +--->| Splice Tx |----------->| Commit Tx |
                 |    +-----------+            +-----------+
                 |    +---------------+        +-----------+
                 +--->| Splice RBF #1 |------->| Commit Tx |
                 |    +---------------+        +-----------+
                 |    +---------------+        +-----------+
                 +--->| Splice RBF #2 |------->| Commit Tx |
                      +---------------+        +-----------+

Eventually, one of the RBF attempts confirms, both nodes send splice_locked for it, and it becomes the new channel funding output:

+---------------+        +-----------+
| Splice RBF #1 |------->| Commit Tx |
+---------------+        +-----------+

Splicing with 0-conf

When using 0-conf, splice_locked will be sent before the splice transaction confirms. But should we allow starting another splice (by sending splice_init) before splice_locked has been exchanged?

If we do this, we can now create chains of pending splice transactions:

+------------+        +-----------+
| Funding Tx |---+--->| Commit Tx |
+------------+   |    +-----------+
                 |    +--------------+        +-----------+
                 +--->| Splice Tx #1 |---+--->| Commit Tx |
                      +--------------+   |    +-----------+
                                         |    +--------------+        +-----------+
                                         +--->| Splice Tx #2 |------->| Commit Tx |
                                              +--------------+        +-----------+

If we don't, we can only have pending splice and must wait for splice_locked to be exchanged before starting another one:

+------------+        +-----------+
| Funding Tx |---+--->| Commit Tx |
+------------+   |    +-----------+
                 |    +--------------+
                 +--->| Splice Tx #1 |
                      +--------------+

After splice_locked has been exchanged for the splice tx:

+--------------+        +-----------+
| Splice Tx #1 |---+--->| Commit Tx |
+--------------+   |    +-----------+
                   |    +--------------+        +-----------+
                   +--->| Splice Tx #2 |------->| Commit Tx |
                        +--------------+        +-----------+

The reason eclair allows the first option is because:

  1. We want to send splice_locked only once we've successfully published the splice transaction
  2. If that creates a noticeable delay before our splice_locked, we don't want to harm the UX by rejecting future splice attempts

Getting 0-conf splice transactions confirmed

0-conf brings another challenge to the table: once you've created a 0-conf splice transaction, what do you do if it doesn't confirm?

Since you have already exchanged splice_locked for this transaction, you cannot RBF it: you can only CPFP by creating another splice transaction that spends it.

We considered using v3 for splice transactions to help, but it has drawbacks:

  • it creates a visible on-chain footprint (we'd instead like splice transactions to look like any other transaction)
  • it makes it impossible to publish long chains of unconfirmed transactions because of package restrictions

Our current strategy is simply to use a relatively high feerate for splice transactions, to ensure that they can enter mempools and be CPFP-ed by the next splice transaction. This doesn't prevent pinning and is imperfect, but we haven't found a better solution that didn't have additional drawbacks.

Mixing 0-conf and RBF is messy and dangerous

What if we still tried to RBF those 0-conf splices? We could avoid sending splice_locked immediately, and instantly RBF if the transaction's feerate is too low to enter our mempool.

I think this is a dead-end, because it introduces many potential issues, and opportunities to steal funds if done incorrectly.

The first issue is that it means we need to wait before sending splice_locked, which is the opposite of what has been commented on the spec. On top of that, what would be the heuristic to decide how long you should wait? Your splice transaction may initially enter your mempool, and then be evicted because higher feerate transactions came in: if you've sent splice_locked before that, you've burnt your opportunity to RBF and can only use CPFP at that point, which is what we started with.

Let's ignore that first point anyway. If you successfully create an RBF transaction, you must now wait for confirmations to see which RBF attempt actually confirms. You cannot accept new splice transactions: 0-conf is temporarily disabled. This creates a very confusing UX, where users will have a hard time understanding why some of their payments are rejected (or delayed).

Open question

Unless someone can figure out a satisfying solution to issues, it seems like we're down to the two following choices:

  1. Disallow send splice_init until splice_locked has been exchanged and send splice_locked immediately.
  2. Allow sending splice_init before splice_locked and manage chains of pending splices.

I believe that the first option can be done if you're careful about never double-spending yourself after sending splice_locked. But the second option is IMO no more complex to implement, and lets you rely on bitcoind (or any other implementation of an on-chain wallet) to prevent accidental double-spending instead of having to manage this complexity inside your lightning software.

Q: What is 0-conf splicing?

A: 0-conf splicing is a feature in the Lightning Network that allows for the creation of splice transactions without waiting for the previous transaction to confirm. This can lead to faster and more efficient channel funding, but also introduces new challenges and complexities.

Q: What are the challenges of 0-conf splicing?

A: One of the main challenges of 0-conf splicing is that it can lead to double-spending if not implemented correctly. Additionally, it can create chains of pending splice transactions, which can be difficult to manage and may lead to pinning issues.

Q: What is pinning?

A: Pinning refers to the situation where a splice transaction is unable to get confirmed quickly, and remains in the mempool for an extended period of time. This can lead to a backlog of unconfirmed transactions and may cause issues with the Lightning Network.

Q: How can I prevent double-spending with 0-conf splicing?

A: To prevent double-spending with 0-conf splicing, it is essential to implement a mechanism that ensures that the same input is not spent twice. This can be achieved by using a combination of techniques, such as using a separate output for each splice transaction and implementing a locking mechanism to prevent double-spending.

Q: What is the difference between RBF and CPFP?

A: RBF (Replace-By-Fee) is a mechanism that allows a new transaction to replace an existing one if it has a higher fee. CPFP (Child-Pays-For-Parent) is a mechanism that allows a new transaction to pay for the fees of an existing transaction. In the context of 0-conf splicing, RBF is used to replace a failed splice transaction with a new one, while CPFP is used to pay for the fees of the new transaction.

Q: How can I manage chains of pending splice transactions?

A: Managing chains of pending splice transactions can be complex and requires careful implementation. One approach is to use a locking mechanism to prevent double-spending and ensure that each splice transaction is processed in the correct order. Another approach is to use a separate output for each splice transaction and implement a mechanism to prevent pinning.

Q: What are the potential risks of mixing 0-conf and RBF?

A: Mixing 0-conf and RBF can lead to a range of potential risks, including double-spending, pinning, and confusion among users. It is essential to carefully consider the implications of mixing these two mechanisms and to implement a robust and secure solution.

Q: What is the current state of 0-conf splicing in the Lightning Network?

A: The current state of 0-conf splicing in the Lightning Network is still evolving and is subject to ongoing development and testing. While some implementations have already adopted 0-conf splicing, others are still in the process of implementing and testing this feature.

Q: What are the future plans for 0-conf splicing in the Lightning Network?

A: The future plans for 0-conf splicing in the Lightning Network are still being developed and refined. However, it is expected that 0-conf splicing will become a key feature of the Lightning Network, enabling faster and more efficient channel funding and improving the overall user experience.