This writeup covers a subset of BIP125 issues mainly with respect to smart contracts on Bitcoin, as well as some proposed policy updates that allow a broad set of improvements to currently deployed and future systems.
The original transactions signal replaceability explicitly or through inheritance as described in the above Summary section.
The language here and elsewhere has led to confusion about intent, and reports such as: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-31876
Regardless of intent, in Core policy today a parent explicitly signaling replaceability will not imply the child is directly replaceable by double-spending the child's inputs. This has implications for smart contracts where we would want to guarentee that a child tx, freely generated by any protocol participant, must also be opt-in RBF.
We can side-step this by requiring "0 CSV" to be executed for those conditions in the execution script, but this only works if the participants support these script fragments.
The replacement transaction may only include an unconfirmed input if that input was included in one of the original transactions. (An unconfirmed input spends an output from a currently-unconfirmed transaction.)
This implies that in any protocol that requires new inputs to be brought in dynamically for fees or updates, a wallet should keep a reserve of confirmed outputs.
Second least-problematic rule behind #4 in my opinion.
The replacement transaction pays an absolute fee of at least the sum paid by the original transactions.
This rule means if there are any shared transactions of any kind, trivial ways of making RBF prohibitively expensive are available:
i) payments: destination(s) can sweep unconfirmed output ii) LN channels: commitment transaction pinning iii) eltoo: update transaction or settlement transaction pinning iv) payment pools: pinning via unilateral exits
To date, these tactics are not seen often. However, in building truly trustless systems, we must eliminate these vulnerabilities while balancing DoS risk with miner incentives.
Today's anchor outputs in Lightning Network BOLTs give both parties the ability to CPFP a specific transaction, as long as you know about the transaction you are to build off of being in the mempool or block.
With two anchor outputs, the counterparty can put a low-feerate package in the mempool to block the other output from being spent due to package limits, the default being 25 descendants(including the root unconfirmed transaction), or 101kvB. Note that these are defaults that may be changed by operators.
To avoid this package limit pinning vector, the not-beloved-by-anyone relay policy called "carve-out" was included: If we hit package limits, let one more pretty-small transaction in from another output from the root parent transaction. This works for exactly two parties, one anchor each, relies on the fact that someone will sweep the just-above-dust outputs to not pollute the utxo set, and leeches those dusty values from the contract.
Two party-only restriction also means we need to expand this to N, or rethink how it can be taken out.
The replacement transaction must also pay for its own bandwidth at or above the rate set by the node's minimum relay fee setting. For example, if the minimum relay fee is 1 satoshi/byte and the replacement transaction is 500 bytes total, then the replacement must pay a fee at least 500 satoshis higher than the sum of the originals.
I've yet to really hear anyone complain about this from an application developer point of view.
The number of original transactions to be replaced and their descendant transactions which will be evicted from the mempool must not exceed a total of 100 transactions.
Given default mempool policies of 25 ancestors and descendants, this means given a set of unconfirmed transactions, you can RBF up to 100/25=4 of these packages in a single fee bump, even if counterparties are creating large transaction chains. Note that this assumption falls apart if miners decide to increase their own policy limit, e.g. maximum descendant count of 50 implies 100/50=2 packages being able to be bumped.
Worst case scenario is all miners increase this value to 101, making it impossible to know if a single RBF can be achieved, regardless of fees paid, regardless if we fixed rule#3!
These are things this proposal does not do.
There are fairly extensive debates on how to do this properly, with concern focusing on anti-DoS protection being lost, as well as the ability for a few in-pool replacements to clear out a nearly unbounded number of data. (FIXME what's the mempool emptying attack again? rule#5 seems to stop that?)
Changing rule#3 is the "third rail" of policy discussions, and as such I propose no changes here.
Given the long history of debates about how to fix these issues, what is a minimum viable fix in policy that can have maximum impact?
Below is one proposal.
These two are "required" pieces to enable a broad array of improvements to smart contracts, including secure eltoo assuming APO-like functionality deployed.
Thanks Gloria! Required tool that lets us hook into N 0-fee tranactions to get them into the mempool with a fee-bringing child. Same BIP125 RBF limitations, roughly.
Make nVersion==3 be standard, with the following additional restrictions for relay: i) All child transactions must also be nVersion==3 ii) All nVersion==3 transactions are taken as explicitly signaling bip125 replacement, regardless of nSequence values set. iii) All nVersion==3 transactions are constrained further to only be standard if the entire mempool package they are entering(itself included) is below a new weight limit of opt_weight_limit. iv) Scale the 100 in rule#5 by replacing with (max(ancestor_count_max, descendant_count_max) - 1) * num_chains
Rationale:
i+ii) We don't want to deal with "inherited signaling" confusion of implementation complexity, while forcing CPFP transactions to signal replaceability. We don't want to re-litigate opt-in RBF either. Smart contract wallets will set this, and retail shops can do 0-conf, or not based on this signaling. A hack to force signaling is to stick "0 CSV" in the execution script but why bother with a hack when we can just fix policy?
iii) opt_weight_limit <- bikeshedding goes here. This is the mitigation for rule#3 to not break your own bank with potentially no cost to the attacker. FIXME do we need to allow "next block" transactions in, even if it's hitting this limit, to make people feel better about miner incentives?
iv) Default values of 25, 25, and fixed value of 4 respectively, resulting in up to 96 transactions.
We want an API where know that we can bump up to num_chains(4?) transactions, even if miners have messed
with mempool default limits in perhaps reasonable ways. If miners are accepting longer chains, more
should be allowed to be evicted, and vice versa. Provided the smart contract is staying within the
confines of the current package relay proposal, this means that the "honest" player
is allowed attach a single CPFP transaction to num_chains outputs, regardless of package limits, except
for the case of descendant_count_max
<=1, meaning no unconfirmed spends allowed at all.
When considering a deduplicated relay package for inclusion in the mempool, if an output generated in the package is also consumed within the package(meaning individual transaction submission does not suffice for inclusion): i) Ignore standard dust relay checks ii) Allow a blank scriptpubkey(or OP_TRUE? malleability questions).
Rationale:
Dust utxos are only problematic in that if they are not spent, full nodes must carry them forever as state, or relies on undeployed relay improvements such as utreexo, which carries its own engineering tradeoffs. If the parent transaction is only included in the case of the child immediately spending it to bring the package feerate high enough for inclusion, we allow this ephemeral dust to be generated.
With 0-value outputs becoming practical for CPFP, we can now re-consider the necessity of multiple anchor-like outputs in protocols. Indeed, we can use a single "anyonecanspend" output as an anchor, in conjunction with the aforementioned nVersion==3 policy change to avoid BIP125 rule#3 pinning attacks.
Note that sticking with N anchors attributed to each party no longer works as they will not be both immediately used in unilateral closes in LN like constructs, violating the restriction that it must be spent in the same package.
What do the proposed changes allow?
RBF batched payments without fear under the opt_weight_limit, even if people are sweeping unconfirmed outputs.
Switch to using nVersion==3 for all pre-signed transactions.
The user can confidently RBF an opposing commitment transaction, or their own, without ever requiring mempool access or assumptions about the mempool view, all with the total rule#3 "damage" being strictly bounded by opt_weight_limit.
With the stretch goals, we can collapse 2 anchor outputs into 1 0-value anchor output. This means no value in the commitment transaction has to be set aside to pay for on-chain fees(thanks to base package relay), as well as no value allocated to anchor output. All fee values can be brought about unilaterally if desired.
Open question: can cooperative opens and close transactions still can use nVersion==2, so in the common case nothing otherwise unusual is seen on chain? These can be joint transactions in the collaborative funding case, which may fall prey to the same pinning issues? If you put funds in, you care on completing or getting money back in a timely fashion.
Assuming APO-like softfork is deployed, we again use nVersion==3 for all pre-signed transactions
The same conversion can be done for eltoo constructions for 1-input-1-output update transactions, either Bring Your Own Fees(BYOF) for additional fee inputs/outputs, or switch to SIGHASH_ALL and include a single 0-value anchor output to use in an identical fashion to ln-penalty as proposed above.
For settlement transactions which have N-party balance outputs and M HTLC outputs, we 1 CSV timelock all of these, and attach a single 0-value anchor.
This obviates the requirement for additional research into SIGHASH_GROUP and advanced sighash consensus modifications.
Note how we don't need carveout for any of these constructions?
What constructions will still need it, aside from legacy?
ACKNOWLEDGEMENTS: Suhas/BlueMatt/??? for the original "descendants must be below X weight total" discussion Antoine for breaking old version of the weight-limiting fix which didn't cover ancestors in addition to the original tx: https://bitcoinops.org/en/newsletters/2022/05/18/#using-transaction-introspection-to-prevent-rbf-pinning Jeremy for spurring discussion about allowing 0-value as long as they are spent, which seems a natural fit for package relay proposal
TODO Fix FIXMEs TODO Add more citations
Thanks for putting this together! A few comments.
It would be great if
opt_weight_limit
wasn't a static value but rather something that could be configured by the contract, for example by setting its value in the taproot annex or something similar.We also need to clearly highlight that the package to which this weight applies consists of all mempool descendants and ancestors.
Good question! I didn't realize until now that pinning attacks also applied to dual-funding transactions, splicing transactions and mutual close transactions, and that pinning risk is real...we'll probably want to use
nVersion = 3
which tells everyone that we're using some kind of contract, which isn't great for privacy unless everyone upgrades to usingnVersion = 3
at some point.