stacks-network / sbtc

Repo containing sbtc
GNU General Public License v3.0
209 stars 3 forks source link

[Design]: Signer Handling Unconfirmed BTC Withdrawals #25

Closed AshtonStephens closed 5 months ago

AshtonStephens commented 5 months ago

Completing the issue description and arriving at a conclusion is the deliverable of this issue.

Design - Signer Handling Unconfirmed BTC Withdrawals

This ticket holds the design around how the Signers will behave when the withdrawal transaction does not get confirmed by the Bitcoin bock and how it fits into sBTC-v1.

1. Summary

Signers will use replace-by-fee (RBF) to ensure that the earliest unconfirmed withdrawal is always at a mempool-clearing rate.

2. Context & Purpose

It's important for sBTC withdrawals to be confirmed quickly. In some cases, signers will broadcast a withdrawal with a fee rate that is too low. This will result in the withdraw being confirmed more slowly than we desire.

2.1 Replace-by-fee (RBF)

RBF is a mechanism for replacing a mempool transaction with another one that has a higher fee.

Bitcoin transaction inputs have a sequence field, which is an integer. This "sequence" field is primarily used for RBF. If a new mempool transaction has the same inputs, but with a higher fee-rate and sequence on each input, Bitcoin nodes will replace the original transaction with the new one in the mempool.

2.2 sBTC fee rate

This design assumes that there is an "sBTC fee rate", agreed upon by signers, which determines the fee rate that sBTC users must pay to execute withdrawals and deposits. This design does not go into the mechanisms for how this fee rate is determined. Instead, this design assumes that signers are always aware of this "sBTC fee rate", and they can use that to execute aspects of this design.

Relevant Research Discussions

Handling partially overlapping signer sets sBTC withdrawals

External Resources

Transaction replacement - Bitcoin wiki

3. Design

3.1 Proposed Component Design

In between Bitcoin blocks, signers will monitor mempool fee rates and use RBF to bump the fee rate of unconfirmed withdrawals. At a high level, they will repeat the following process:

  1. Given the current sBTC fee rate, select all pending withdrawals that have a high enough "max fee rate" to cover the fee rate. This results in a set of withdrawals.
  2. As per Handling partially overlapping signer sets, signers construct a set of withdrawal transactions.
  3. Signers use the "sBTC UTXO", which is the latest confirmed sBTC transaction on Bitcoin, and use this as an input to the chain of withdrawal transactions.
  4. Each time this loop is repeated, the sequence of the sBTC UTXO input is increased monotonically, starting at 0.
    1. This requires the signers to be able to keep track of pending withdrawals and their input's sequences.
    2. One way to refer to this is as the "sBTC UTXO sequence".
    3. Only the sBTC UTXO's input needs to be increased. Subsequent withdrawals will have a different fee rate, and thus will have a different TXID.
  5. Broadcast the withdrawal transactions
  6. If the current mempool fee rate is too high for our pending withdrawals to be confirmed in the next block, determine the new "sBTC fee rate" and restart at step 1.

3.2 Design Diagram

A high-level diagram of using RBF to re-broadcast pending withdrawals is included below:

Image

The important aspect of the diagram with relation to this design is the step where sequence is set on the sBTC UTXO input. By monotonically increasing this sequence and using a higher fee rate, new withdrawal transactions replace previous ones in the BTC mempool.

Here's a diagram that shows how this "loop" can be ran more than once. In the second loop, a new withdrawal request has come in, and the original withdrawal transaction is replaced with a new transaction.

Image

3.3 Considerations & Alternatives

3.3.1 CPFP

Child-pays-for-parent is an alternative approach to bumping the fee rate of a pending transaction. It is likely that RBF is a better approach, because it has less complexities regarding the max_fee that is included in withdrawal requests. CPFP requires the child transactions to pay a higher per-transaction fee rate, meaning the withdrawal requests would receive less BTC than in the parent transactions.

CPFP is a mechanism on Bitcoin that allows the fee rate of an unconfirmed transaction to be increased via a higher-fee on a "child" transaction that uses it as an input.

With CPFP, the resultant fee rate of two transactions is:

sum(size) / sum(fees)

To illustrate CPFP with an example:

  1. Transaction A is broadcasted
    1. vBytes (size) is 10
    2. Sats fee is 100
    3. Thus, initial sats/vB is 10
  2. Transaction B is broadcasted with A as an input
    1. vBytes is 10
    2. Sats fee is 300
    3. sats/vByte rate is 30
  3. Because transaction B has a higher fee, miners use CPFP fee calculations to figure out the fee rate of including both transactions
    1. Total size is 20
    2. Total fee is 400
    3. Thus, the effective fee rate of including both transactions is 20 sats/vBytes

As new sBTC withdrawal requests come in, signers can use CPFP with these new withdrawal requests to increase the fee of any unconfirmed withdrawals.

To start, assume there is unconfirmed withdrawal wA, which is processing withdrawal request rA. wA is currently in the mempool. The sBTC fee rate is fA.

In a later Stacks block, withdrawal request rB is created. At the same time, the sBTC fee rate has increased to fB.

While generating withdrawal transaction wB, signers use the new fee rate, fB, to ensure that both wA and wB have an effective fee rate of fB.

The do so, signers need to keep track of each pending withdrawal transaction's size, along with that transaction's fixed fee (in Sats - not fee rate). Signers then use CPFP math to determine the fee that is needed for wB. This is done by summing the size (in vBytes) of each pending withdrawal and then calculating the necessary fee needed for wB to hit the fee rate fB.

As an example:

  1. The total size of all pending withdrawals is 10 vBytes, and the total fees are 100 sats
  2. Now, the sBTC fee rate is 20
  3. The size of the new withdrawal (wB) is 10 vBytes
  4. To reach an effective fee rate of 20 sats/vB, the fee for wB needs to be 300 sats
20 (fB) = (100 + wB_fee) / (10 + 10)
wB_fee = 300 sats
3.3.1.1 Constraints of CPFP

Withdrawal requests include a max_fee parameter, which determines the maximum amount of Sats that can be used to process a withdrawal. If the sBTC fee rate goes over that amount, the withdrawal should not be processed.

This constraint doesn't work well with CPFP, because it requires the "child" withdrawal requests to pay a higher per-transaction fee than the child transactions. Thus, using CPFP would require us to unfairly send less BTC to the child requests than the "parent" requests.

3.2 Areas of Ambiguity


Closing Checklist

AshtonStephens commented 5 months ago

Are there going to be differing opinions on how to get the mempool fee rate?

netrome commented 5 months ago

Tiny consideration. Imo, the user should control their max_fee and not max_fee_rate. The signers should do the conversion between a fee and a fee rate.

AshtonStephens commented 5 months ago

Question: Should the withdrawal say "max fee" and attempt to RBF to have the fewest sats/vByte or just do the max.

hstove commented 5 months ago

Are there going to be differing opinions on how to get the mempool fee rate?

There isn't a "canonical" way to do this, but there is an RPC endpoint for getting a current market fee. All of the other options require using a third-party dependency (like Electrum or mempool.space), which might not be desirable.

AshtonStephens commented 5 months ago

Committing to the "max fee" approach. We need a concrete design for the details of consolidating like choosing a fee and removing other withdrawals.

netrome commented 5 months ago

Committing to "max fee" instead of "max fee rate"

SundarGowtham commented 5 months ago

This is more of an UI/UX question but what if some transactions (let's say 50%) do not get included because their max fee is too low, a new signing round happens, then bitcoin fees spike again and you remain in a constant stale state of cutting the bottom half of transactions for a long while? How will we mitigate this scenario?

AshtonStephens commented 5 months ago

Committing to "max fee" instead of "max fee rate"

I don't understand the difference between these two actually. I thought we opted to finalize "not specifying flat fee; specifing flat fee rate."

AshtonStephens commented 5 months ago

@netrome can we close this? Does the result of this make sense to include the design of handling this in https://github.com/Trust-Machines/sbtc-v1/issues/16?

netrome commented 5 months ago

@netrome can we close this? Does the result of this make sense to include the design of handling this in #16?

Yes we can close this. I'd prefer to keep the withdrawal flow lean, and view this as an elaboration of the "Signers broadcast a "Withdrawal accept" transaction on Bitcoin" step already in the chart.

AshtonStephens commented 5 months ago

This is more of an UI/UX question but what if some transactions (let's say 50%) do not get included because their max fee is too low, a new signing round happens, then bitcoin fees spike again and you remain in a constant stale state of cutting the bottom half of transactions for a long while? How will we mitigate this scenario?

@SundarGowtham lets say someone makes a withdrawal and says the max they're willing to pay is something unrealistically low like 1 sat. There will need to be some hard method of rejecting that case, and I strongly recommend that all cases where the transaction fee is too low are handled the same way.

For example: maybe if two sortitions pass and the withdrawal request has too low of a fee there's some rejection that gets sent to the clarity contract.

I'll make a design discussion for handling the nuances of this like we discussed in the call

AshtonStephens commented 5 months ago

Created https://github.com/Trust-Machines/sbtc-v1/issues/43 which should specify the clarity and signer behavior details around rejecting withdrawals from batches and how the max fee is determined.