Mempool validation enforces ancestor and descendant count
limits,
requiring that no mempool transaction have more than 24 (25 with CPFP carve out) descendants.
When a node receives a transaction that conflicts with, or spends the same prevout
as, one or more of the transactions in its mempool, it decides which transaction(s) to keep based
on a set of rules. Bitcoin Core’s Replace by Fee
policy
requires that no transaction replace more than 100 mempool transactions (“Rule 5”).
Many people conclude that the descendant limit makes Rule 5 redundant; it seems that a transaction
cannot replace more than 100 transactions a conflicting mempool transaction cannot have more than
25 descendants. This is a very common misconception. A transaction can spend multiple prevouts, and
thus conflict with multiple unrelated transactions.
What does it mean for a transaction to “conflict with” transactions in the mempool? What is the
difference between a “directly” and “indirectly conflicting” transaction?
Based on the default RBF policy, how many “direct” conflicts is a transaction allowed to have?
How many transactions is it allowed to replace?
Why should the node limit the number of transactions that can be replaced at a time? Can you
think of any potential attacks
How is it possible for a transaction to conflict with 100 transactions if the descendant limit is
25? Can you come up with an example that isn’t the one tested in this PR?
What’s wrong with configuring -acceptnonstdtxn=1, -limitancestorcount, -limitancestorsize,
-limitdescendantcount, and -limitdescendantsize, to test mempool policy?
Why is it necessary to pass a different sequence to create_self_transfer_multi?
What does annotating get_utxo with the -> dict return type annotation do?
Bonus Questions
Rule 5 only restricts the number of transactions that can be replaced, not the size. However, an
effective maximum exists; what is the effective maximum virtual size of transactions that can be
replaced in a default mempool? (Hint: default maximum transaction
weight
and maximum ancestor/descendant
limits
(Hint Hint: not all of these numbers are relevant)).
Hypothetically, if we increased the default ancestor/descendant limits to 120, would we also need
to change the limit on replaced transactions? (Hint: how can a transaction recipient prevent its
replacement?)
In what scenarios will the
code
for calculating the number of to-be-replaced transactions overestimate? If we call
pool.CalculateDescendants() with a set of 99 mempool entries, what is the maximum number of
mempool transactions we might traverse before the function returns?
<danielabrozzoni> I read the code, I tried to understand the difference between the new `test_too_many_replacements_with_default_mempool_params` and the old `test_too_many_replacements` to understand why the old one wasn't testing the rule 5 use case
<glozow> larryruane: danielabrozzoni: Great summaries, thank you! Let's move onto the questions. What does it mean for a transaction to “conflict with” transactions in the mempool? What is the difference between a “directly” and “indirectly conflicting” transaction?
<larryruane> i think indirect would be if the existing transaction has decendants ... those would have to be dropped from the mempool too, if this new tx is accepted
<danielabrozzoni> TxA is directly conflicting with TxB if A double spends B's inputs. TxA is indirectly conflicting with TxB if A is directly conflicting with one of B's ancestors (so if B's ancestor is replaced with A, B has to be evicted as well).
<glozow> danielabrozzoni: larryruane: yes! if you evict a transaction, you must also evict its descendants. so we also care if there are "indirect" conflicts, i.e. the transaction conflicts with the ancestor of a mempool tx
<glozow> Why should the node limit the number of transactions that can be replaced at a time? Can you think of any potential attacks if we don't have a limit?
<larryruane> and just to elaborate slightly, the reason we MUST drop the decendants is because their input references an output by txid (and index), and that txid no longer exists
<larryruane> they could submit (let's just say) 500 independent transactions all with low fee (but enough to make it into the mempool), and then attacker could submit a single transaction that conflicts with all 500
<larryruane> danielabrozzoni: maybe? but also just flushing other nodes' mempools is a kind of DoS because the flushed tx won't get included in a block (unless they're resubmitted)
<danielabrozzoni> But the txs you're replacing, they're all yours, so I'm not sure why that would be a DoS... does someone have something to read on this?
<glozow> How is it possible for a transaction to conflict with 100 transactions if the descendant limit is 25? Can you come up with an example that isn’t the one tested in this PR?
<sipa> 36 vbytes prevout, 4 vbytes nsquence, 1 vbyte scriptsig length, 1 WU for number of witness stack items, 1 WU for the length of the witness stack item, 64 WU for the signature
<glozow> Fun! I have a bonus question that's pretty relevant here. Rule 5 only restricts the number of transactions that can be replaced, not the size. However, an effective maximum exists; what is the effective maximum virtual size of transactions that can be replaced in a default mempool?
<glozow> next question. What’s wrong with configuring -acceptnonstdtxn=1, -limitancestorcount, -limitancestorsize, -limitdescendantcount, and -limitdescendantsize, to test mempool policy?
<glozow> yes there are a lot of approximations here. my point was we can't really just do simple arithmetic to see how many transactions fit in a 300MB mempool
<sipa> michaelfolkson: It helps to realize that transactions are abstract data structures with a lot of complex structure in them (inputs, outputs, witnesses, stack items, ...). The traditional "network protocol raw serialized" view of a transaction is just one way of representing it, and isn't actually used internally except for storing on disk and transmitting over the network.
<danielabrozzoni> The transactions that need to be replaced, or that replace, need to signal for RBF, and the way this is done is by setting the sequence to less than 0xffffffff - 1