-
Notifications
You must be signed in to change notification settings - Fork 35.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
mempool / miner: regularly flush <=0-fee entries and mine everything in the mempool #27018
Conversation
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ReviewsSee the guideline for information on the review process. ConflictsNo conflicts as of last run. |
3b748b8
to
bda9da9
Compare
cc @naumenkogs Re: #26933 (comment) Just re-posting the scenario with 2400 extra transactions to evict in an RBF (originally in #26933 description):
A counterargument is that we don't currently have any guarantees that we never |
I guess another similar question is whether we care about the potential reorg handling slowdown. One scenario is a reorg adding below-minrelayfeerate transactions back to mempool, which we then spend time flushing through |
Adding a trim-under-min-relay directly in Transaction processing not being computationally cheap has already been raised in the context of #21224 As of today, I think we would like to ensure second-layers can broadcast significant quantity of pre-signed transactions in a short period of time (e.g a LSP doing splicing with all its spokes to rebalance channels because network mempools are empty), without interferences with our current DoS limits like |
@ariard Can you tell if my description is correct? When Alice and Bob are not friends, Bob can waste Alice's resources by:
And before this PR, the transaction would sit in the mempool with below-minrelayfeerate waiting for empty blocks? So that's a problem? |
I'm also not really understanding the comment. The flushing logic only effects things that can't even be relayed on today's network, assuming no one is touching minrelayfeerate defaults? I did some basic benchmarking here on eviction, showing that on my machine at least it takes ~0.3s to evict a "worst case" scenario. Do note that it also takes roughly the same amount of time to even build up the mempool, and that's without any script checks. So in some sense it at least isn't obviously the wrong thing to do from a CPU exhaustion perspective. #26933 (comment) One sanity benchmark I didn't run is the speed of the benchmark if only the child transaction of each package is evicted. I suspect it would be quick, but probably should double check. This PR seems to be simpler from a mental model perspective, at the cost of some additional CPU cycles. It would also allow non-V3 transactions to have more reasonable specs. #26933 Seems to be more complex from a mental model perspective, but may be more protective of CPU cycles in certain scenarios with non-privileged mempool usage.
I'd consider this roughly equivalent to a mempool user paying to put things in the mempool, then evicting them in terms of attack cost. Am I thinking about this wrong? |
indexed_transaction_set::index<descendant_score>::type::iterator it = mapTx.get<descendant_score>().begin(); | ||
|
||
CFeeRate removed(it->GetModFeesWithDescendants(), it->GetSizeWithDescendants()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be good to rename this -- previously the tx was definitely being removed, but now the removal is conditional. candidate
maybe?
removed += m_incremental_relay_feerate; | ||
trackPackageRemoved(removed); | ||
maxFeeRateRemoved = std::max(maxFeeRateRemoved, removed); | ||
if (removed >= m_min_relay_feerate) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would have been slightly clearer to me as:
if (removed >= m_min_relay_feerate) {
if (DynamicMemoryUsage() <= sizelimit) {
break; // normal exit from loop
}
// only track fee rate for txs with descendant feerate above m_min_relay_feerate
removed += incr; trackPackageRemoved(removed); mFRR = max(mFRR, removed);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're concerned about the 2500 tx thing, we could limit that here couldn't we? ie:
int low_fee_removed = 0;
while (!empty() && low_fee_removed < 100) {
...
if (removed < m_min_relay_feerate && DynamicMemoryUsage() <= sizelimit) {
++low_fee_removed;
}
...
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're concerned about the 2500 tx thing, we could limit that here couldn't we?
I think the problem with limiting this way is we're just putting it off - we still need to finish flushing everything before we build a block template or ATMP another transaction, otherwise we might build a low-feerate block template or let a tx spend from something 0-fee. So we could stop here, then mark that the mempool needs_flush=true
(or something) and flush fully before we do either of those 2 things?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the problem with building a low-feerate block template? The tx already propogated, so should be roughly fine for compact block relay; and if there are higher feerate txs, we'll select them anyway.
If we ATMP a child of the tx, and it CPFPs it enough to get above min_relay_feerate, then that's also fine, we just get the same behaviour with "accept A+B, drop A, accept C(pays for B)" as we do with "accept A+B, accept C, drop A".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding a 0-fee package to a block template can not raise profitability, only potentially lower it(?)
Pretty easy to imagine different ancestors being evicted at different times, causing compact block mismatches. We could retain blockmintxfee
though, right? Then all you're left with is some mismatches across pools potentially causing weirdness with individual vs package evaluation(dedup or not), but this is likely short-lived anyways.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the problem with building a low-feerate block template?
So I think the argument for banning literal 0-fee packages is there is no gain from mining it, but it costs resources to include in the block. So it's probably not controversial to remove everything =0 fee, regardless of how many there are (?).
Beyond that (>0 but <minrelay), I don't have an argument other than the fact that a miner might expect -blockmintxfee
to guarantee that the feerate of their block won't be lower than that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want to support miners being able to guarantee the feerate of their block won't be lower than x
, then we shouldn't be removing/deprecating -blockmintxfee
?
I think it makes sense to have "ancestor fee(rate) is at 0sat? we're done here" logic; miners can always modify the fee if the tx is desired anyway...
So, in trying to come up with a proof that evicting a descendant package that is below the minrelayfeerate from the mempool is safe to do because we'd never evict transactions we'd want to mine, I discovered a counterexample, please see below: |
You can already have the same with a full mempool? Multiply the fees by 10, and set the mempool min fee to 9sat/vb, have the rest of the mempool only spend confirmed txs and have a fee rate of 8.4-9 sat/vb, but have there be enough of them that the mempool is overfull and needs to be trimmed. In that case, we'll drop A (at 8.3sat/vb descendant feerate) first, even though D+A has the highest ancestor score and should be the first thing we'd mine. I don't think you can mine by ancestor score and trim by descendant score without getting these inconsistent edge cases... |
IIUC Mempool trimming when full causes this in very simple situations, such as a 0-fee parent with two fee-paying children. If DF(parent) is dragged down too much by a child, the whole package is evicted even if the other child was paying enough. In other words, I think it opens the door to a more specific form of it when mempools are empty, not a new class of issues.
Would a wallet's rebroadcast of say a parent-child package still similarly as robust if things weren't trimmed? |
I am still wrapping my head around how best to think about this, but a few things come to mind:
Thinking about (2), perhaps we should split our concerns about low-feerate transactions/packages in the mempool into what I think are the two components: (a) anti-DoS and (b) maximizing miner income. For (a) anti-DoS, I don't believe there is a DoS concern if we both mine everything in the mempool and only encounter this situation due to these RBF scenarios where no free relay is going on. So really the concern is just (b), how do we ensure that a miner is never worse off for including some transactions that are in the mempool. We could just try to cut out the most obvious problems -- transactions that are literally 0-fee to the miner. If we restrict the allowed topologies for package validation, we may be able to do better than that, but I don't know if that's a direction we want to go down. @ajtowns' idea in #26933 may work as well, but I'm wondering how we can generalize our thinking of what we're trying to specifically achieve? |
bda9da9
to
2629aea
Compare
If we enforce -minrelaytxfee at the same feerate as -blockmintxfee (currently their defaults are the same) for all transactions coming into the mempool, the BlockAssembler can trust that adding packages will result in a block template meeting this feerate. This strategy may also be better. For example, one transaction is bumped by two children, where the 3-transaction package is above blockMinFeeRate, but each child's ancestor set is below blockMinFeeRate. We do not enforce feerate rules during reorgs, so it is possible for low-feerate transactions to exist in the mempool this way. However, these transactions are only selected if there are no better packages available in the mempool. Having separate values for -minrelaytxfee and -blockmintxfee made sense in the past when Coin Age Priority existed, but that was removed in v0.15.
At this point it's not expected that there are any such transactions, except from reorgs.
2629aea
to
744851d
Compare
…kages bf77fc9 [test] mempool full in package accept (glozow) b51ebcc [validation] set PackageValidationState when mempool full (glozow) 563a2ee [policy] disallow transactions under min relay fee, even in packages (glozow) c4554fe [test] package cpfp bumps parents <mempoolminfee but >=minrelaytxfee (glozow) ac463e8 [test util] mock mempool minimum feerate (glozow) Pull request description: Part of package relay, see #27463. Note that this still allows packages to bump transactions that are below the dynamic mempool minimum feerate, which means this still solves the "mempool is congested and my presigned 1sat/vB tx is screwed" problem for all transactions. On master, the package policy (only accessible through regtest-only RPC submitpackage) allows 0-fee (or otherwise below min relay feerate) transactions if they are bumped by a child. However, with default package limits, we don't yet have a DoS-resistant way of ensuring these transactions remain bumped throughout their time in the mempool. Primarily, the fee-bumping child may later be replaced by another transaction that doesn't bump the parent(s). The parent(s) could potentially stay bumped by other transactions, but not enough to ever be selected by the `BlockAssembler` (due to `blockmintxfee`). For example, (tested [here](https://github.com/glozow/bitcoin/commits/26933-motivation)): - The mempool accepts 24 below-minrelayfeerate transactions ("0-fee parents"), all bumped by a single high-fee transaction ("the fee-bumping child"). The fee-bumping child also spends a confirmed UTXO. - Two additional children are added to each 0-fee parent. These children each pay a feerate slightly above the minimum relay feerate (e.g. 1.9sat/vB) such that, for each 0-fee parent, the total fees of its two children divided by the total size of the children and parent is above the minimum relay feerate. - If a block template is built now, all transactions would be selected. - A transaction replaces the the fee-bumping child, spending only the confirmed UTXO and not any of the outputs from the 0-fee parents. - The 0-fee parents now each have 2 children. Their descendant feerates are above minrelayfeerate, which means that they remain in the mempool, even if the mempool evicts all below-minrelayfeerate packages. - If a block template is built now, none of the 0-fee parents or their children would be selected. - Even more low-feerate descendants can be added to these below-minrelayfeerate packages and they will not be evicted until they expire or the mempool reaches capacity. Unless we have a DoS-resistant way of ensuring package CPFP-bumped transactions are always bumped, allowing package CPFP to bump below-minrelayfeerate transactions can result in these problematic situations. See #27018 which proposes a partial solution with some limitations, and contains discussion about potential improvements to eviction strategy. While no adequate solution exists, for now, avoid these situations by requiring all transactions to meet min relay feerate. ACKs for top commit: ajtowns: reACK bf77fc9 instagibbs: re-ACK bf77fc9 Tree-SHA512: 28940f41493a9e280b010284316fb8caf1ed7b2090ba9a4ef8a3b2eafc5933601074b142f4f7d4e3c6c4cce99d3146f5c8e1393d9406c6f2070dd41c817985c9
Needs rebase if still relevant? |
Closing in favor of #27677 which would solve the more general issue of selection score != eviction score. Noting that #25038 adds trimming of things below min relay feerate, but we only expect those transactions as a result of reorgs or replacement of v3 children. v3 doesn't have the the issue of selection score != eviction score due to topological restrictions. |
Planning to resurrect this after #28948. |
This was suggested in #26933 (comment).
The Problem
Package CPFP (only accessible through regtest-only RPC submitpackage) allows 0-fee (or otherwise below min relay feerate) transactions if they are bumped by a child. We need to know what to do with these transactions if they lose their sponsor, e.g. due to a replacement that removed the input spending this 0-fee transaction.
This is made slightly more complicated by the fact that our "selection scoring" (BlockAssembler) is different from our "eviction scoring." Roughly, we select based on ancestor feerate and evict based on descendant feerate. This may lead to evicting things we would actually want to mine and not evicting things that we will never mine.
This PR's approach is to remove the 1sat/vB
-blockmintxfee
and have BlockAssembler select anything in the mempool (still based on ancestor packages, but not stopping at 1sat/vB). It also adds logic toTrimToSize()
to evict anything paying <=0 fees. The idea is, if we're getting to the bottom of our mempool, we scrape up all the sats we can. Anything that pays some fee is worth adding to the block template.A major advantage of this approach is that 0-fee, non-v3 transactions can be bumped in package CPFP.
A few observations which may or may not be problematic: