The PR branch HEAD was b15079ac7b at the time of this review club meeting.
Notes
The content of a transaction can be separated into two components:
non-witness data
witness data
Since Segwit activation, there are two ways a transaction can be serialized,
with and without the witness data.
A transaction’s txid is the sha256d hash of its non-witness serialization.
A transaction’s wtxid is the sha256d hash of its full (including its witness)
serialization.
(Hashing the serialization of only the witness data isn’t useful.)
It’s possible for two transactions to have the same txid but different
wtxids. The opposite is not possible (same wtxids but different txids).
That is, the mapping from txid to wtxid is one-to-many.
The only place any type of transaction ID appears
on the blockchain is within transaction inputs, each of which contains,
among other things, a
COutPoint.
This object “points” to an output of the source transaction using the
txid (note, not wtxid) of the source transaction and the index
into its outputs array. This is the output that this input is “spending”.
Currently, when a transaction is submitted to the mempool and an
existing mempool transaction has the same txid, the incoming
transaction is immediately rejected.
Replace-by-fee (RBF), as described in
BIP125,
allows transactions that have already been accepted into the mempool to
be replaced by a newly-arriving transaction. Previous to BIP125, an
incoming transaction that spent any of the same outputs as an existing
in-mempool transaction would be rejected as a double-spend attempt.
This has been called the “first seen safe” policy.
If an RBF replacement does occur, any descendant (downstream) transactions
in the mempool must be removed. These are transactions that (recursively)
spend outputs of the transaction being replaced.
The replacement transaction can spend different inputs (except at least
one, or else it wouldn’t conflict and replacement wouldn’t be needed),
can have different outputs, and therefore will always have a different
txid than the transaction(s) being replaced.
There are several conditions that must be met for an RBF replacement to occur, documented
here,
including a requirement that the replacement pay more fees than the original transaction(s).
PR 24007
implements something similar to RBF except the txid of the two transactions is the same
(but the wtxids are different). As it’s not possible for a same-txid-different witness
transaction to include a different absolute fee amount, the rules for witness replacement differ
from that of regular RBF.
A wtxid “commits to” an entire transaction, including its witness,
while a txid does not. What does the phrase “commits to” mean in Bitcoin?
Is the mempool “indexed” by txid or wtxid? Equivalently, we can
ask: Can the mempool contain multiple transactions with the same txid
(but different wtxids)? If not, would it make sense for it to do so?
Does this PR change a consensus rule? Why or why not? What happens
if some nodes are running this PR and their peers are not?
Should Bitcoin Core policies be miner incentive-compatible?
When an RBF replacement occurs, why is it necessary to remove the
descendant transactions?
When a witness replacement occurs, is it necessary to remove the
descendant transactions? Why or why not?
This PR allows replacement even if the existing transaction hasn’t
signaled replaceability. Is this an oversight?
Why would a witness-replacement transaction be broadcast? Why not
broadcast the replacement transaction initially? What is this PR’s
use case?
How can a transaction have multiple possible witness data that are
different sizes? (Hint: see the
test!)
The PR
requires
the replacement transaction’s size to be 95% or less than the
size of the replaced transaction. Why can’t it just use the normal RBF rules?
Why is this
check written as (simplified) new_size * 100 >= old_size * 95 rather than the more obvious
new_size >= old_size * 0.95 ?
(Extra credit) How does witness replacement interact with packages?
<theStack_> the PR enables to replace txs in the mempool if only the witness data changes, but the remaining parts of the transactions are unchanged (i.e. same txid, different wtxid)
<OliverOffing> this PR allows users to replace the witness data for a transaction in the mempool, as long as the new witness data is smaller (i.e. higher fee _rate_)
<stickies-v> If you want to change (and broadcast) the witness of an already broadcasted transaction (e.g. by spending a different path in the script), this is currently not allowed because the txid doesn't change when just the witness data changes. This PR allows such updated transactions to be acceptedin mempool and broadcasted, provided that the new witness is sufficiently small enough.
<larryruane> ziggie: theStack_: OliverOffing - correct, what's the current policy if a transaction arrives at the mempool but there's already a tx with the same txid?
<stickies-v> txid is calculated as the hash of the serialized transaction without witness data (and thus malleable), wtxid is the hash of the serialized tx with witness data
<larryruane> like, in this case, we say the wtxid "commits to" the witness, and you've all said the witness is included in computing wtxid, so why is it called that?
<stickies-v> signatures are also malleable (hence segwit), so I think even without segwit that could be the case. It wouldn't hit the 5% witness size decrease requirement of this PR though, just for completeness
<larryruane> an tx output can be thought of as a lock, but the lock can possibly be unlockable with different "keys" ... each "key" corresponds to a different witness (very high level description)
<larryruane> Okay, feel free to continue this line of thought, but if we're ready to move to the mempool ... the mempool is a collection of unconfirmed transactions, each represented by key-value pair (with unique keys, like a std::map) ... what is the key?
<larryruane> OliverOffing: perfect, that's exactly it! transactions in the mempool can _never_ be conflicting, and two transactions with the same txid are necessarily conflicting (spending the same inputs)
<larryruane> glozow: stickies-v: this lets us *look up* transactions by wtxid, but there can still not be two transactions with the same txid, right? I hope I have that right!
<stickies-v> larryruane it doesn't get accepted in mempool because it wouldn't pass MemPoolAccept::PreChecks but I don't think the CTxMempool wouldn't technically be able to?
<glozow> if the question is whether boost multi index container will let us have 2 entries, i'll need to go read the docs. if the question is whether our node will survive if we put 2 transactions with the same txid in the mempool, the answer is no
<larryruane> stickies-v: good point, I'm not sure if anything actually enforces no duplicate txids (other than conflicting spends), like, if you happened to have a collision
<larryruane> svav: that's a great question, the answer is yes and no ... It's yes in the sense that two tx with the same txid must have the same *effect* (let's review, what is the effect of a tx? It's to destroy some UTXOs and create some new ones) ... but these two tx can be *enabled* by different witnesses, so they're different in that way ... Do I have this right? (I'm kind of new to this myself!)
<larryruane> stickies-v: maybe, I think interestingly enough, if a node receives a tx with a txid that is already in its mempool, it re-broadcasts its *existing* tx - is that right glozow ?
<OliverOffing> different nodes would keep different transactions in the mempool but that's not really a problem for the network—only for the user trying to replace their tx's wit data as stickies-v mentioned
<glozow> a mempool is as useful as it is an accurate reflection of what's in the miners mempools. being incentive-incompatible is a good way to deviate from what would be in a miner's mempool. so yes.
<stickies-v> I think so. If not, you encourage miners to set up individual backchannels (e.g. sending tx directly to miner for a direct fee) to help get non-standard transactions mined, and that puts smaller/anonymous miners at a disadvantage
<theStack_> i think it should be, if i understand "miner-incentive compatible" correctly; we want miners to maximize their fees in order to increase the security
<larryruane> here's a crazy thought, BTW (not in the notes), we sometimes have these policies that we're not sure miners are following - maybe we can look at the tx that arrive in each *mined block* and dynamically adjust our policies to match!
<stickies-v> erik-etsuji-kato I'm not sure that's relevant here actually, because those transactions don't get propagated to the network (because then if someone else mines them, the miner loses money)
<stickies-v> hmm they don't need to collude I think, any dishonest miner can do this, it's trivial to just construct some high fee tx's and inject them only into your own block. I just mean that this wouldn't affect policy, because they're not propagated out of block
<larryruane> okay let's see (but again, feel free to keep going on any previous thread), question 6, quick detour to RBF, when RBF occurs, why is it necessary to remove mempool decendants? Is that necessary for witness-replacement?
<larryruane> maybe we can cover 8 quickly, why does this PR allow witness-replacement even if the tx hasn't signaled replacability (which is required for RBF)?
<stickies-v> I think this would break protocols that rely on unconfirmed wtxids, but I don't think those protocols currently exist and that seems like an unreliable thing to do anyway so we probably shouldn't care?
<theStack_> though, even without RBF signalling there is a reason to be worried, as the tx could just have a too-low fee and eventually get kicked out of the mempool, and _then_ get replaced? (probably a different discussion though)
<larryruane> OliverOffing: fair point ... So there's that question (9), and there are a few more questions (11-13) but only a few minutes left, anyone like to choose anything else to discuss?
<glozow> re: use cases, it seem like we would only be interested in this if there's a pinning attack. like, you have counterparties on your transaction and somebody bloats up the witness by thousands of vbytes or something. i can't imagine why a normal user is broadcasting their own transaction multiple times with various witnesses
<stickies-v> larryruane I can potentially see this being useful in protocols where one party signals they're willing to go on chain in a suboptimal path by broadcasting that larger transaction, maybe encouraging all parties to instead lower the fees and go for key spend (in taproot case) instead
<larryruane> makes sense ... I guess overall, maybe the main argument is that allowing witness replacement is compatible with miner incentive, and it's not very complex
<theStack_> for question 12, i think the reason for multiplying both sides with an integer rather than one side with a float is to avoid floating-point arithmetics?
<monlovesmango> larryruane: for the attack mentioned in that post, wouldn't it make more send to require the witness to be smaller by a certain byte size rather than percentage to eliminate this attack vector? or is there a reason we are using percentage of data size?
<glozow> larryruane: do you know of any applications where you share an input with an untrusted counterparty who might be able to broadcast with a different witness to grief you?