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
Continuously limit the memory pool memory consumption #6421
Conversation
Needs rebase because #6410 is merged now. |
// Don't accept it if it can't get into a block | ||
CAmount txMinFee = GetMinRelayFee(tx, nSize, true); | ||
CAmount txMinFee = GetMinRelayFee(tx, nSize + nSizeDeleted, true); | ||
if (fLimitFree && nFees < txMinFee) | ||
return state.DoS(0, error("AcceptToMemoryPool: not enough fees %s, %d < %d", | ||
hash.ToString(), nFees, txMinFee), |
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.
Need to update this error msg.
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.
Suggestion?
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.
Oh, actually I misread it; no changes needed.
@@ -50,6 +50,8 @@ struct CNodeStateStats; | |||
static const bool DEFAULT_ALERTS = true; | |||
/** Default for -maxorphantx, maximum number of orphan transactions kept in memory */ | |||
static const unsigned int DEFAULT_MAX_ORPHAN_TRANSACTIONS = 100; | |||
/** Default for -maxmempool, maximum megabytes of the mempool */ | |||
static const unsigned int DEFAULT_MAX_MEMPOOL_SIZE = 300; |
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.
Can you update this comment to clarify if we're talking about txs or ram?
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.
Fixed.
3c638fc
to
800927a
Compare
Nits from #6331 rebased at sipa/bitcoin@limitpool...jtimon:pr-6421-0.11.99 (will force push to jtimon/pr-6421-0.11.99 as this gets rebased). |
7ddb9d0
to
9c95434
Compare
@lapp0 You suggested using "the new transaction's fees should pay for the size of the new plus removed transaction". That doesn't help, as you can create a sequence of transactions that each replace the previous one, and each have enough fees to pay for both. This would give you infinite relay bandwidth at fixed cost. The solution is perhaps to remember for each mempool transaction what the size of everything it has replaced is, but that's a bit more complex than I'm willing to do now. I've chosen the conservative approach here which is to look at the fee difference instead. |
@jtimon It's probably a bit more complicated than just a score function, I now realize. The mempool code is trying to optimize for fee/byte (currently), independently of what sorting is implemented by the index. I think we'll need a policy-controlled "cost" (as a generalization of size, perhaps corrected for UTXO differences) and policy-controlled "revenue" (as a generalization of fee). The reason is that you can't compute the "score" of a collection of transactions - you need (revenue1+revenue2)/(cost1+cost2) rather than just score1+score2. |
Slightly tested. Running this code (git commit tip 3c638fc0ba82a9d9c235f428d098a098fc0b6b16, not the latest tip) since some hours with Log filtered after the "stored orphan txs": https://gist.githubusercontent.com/jonasschnelli/1f2e89d64887710f6c5b/raw/dba3d68d79cc649cd7e01c992d40da8d46073431/gistfile1.txt |
I believe this could be much simpler (and the end result better) after #5709 (is 10 commits but ready to be squashed into the first one), but I doubt people want to read step by step to be sure behavior is not being changed. At least not more now than when it was opened... |
@jtimon It's a bit more complicated. The replacement code needs to have a way to know whether replacing a set of transactions with another transaction is a good idea. Contrary to what I first thought, just having a score to compare is not enough - if the index order doesn't match the feerate well, its search for sets to remove will degrade or fail. One way to generalize this to something policy-controllable is to have a "general reward" (typically fee) and a "general cost" (typically bytes) determined by the policy at mempool entry time, and then compare reward/cost ratios (typically feerates), both in the index, and in the replacement code (the limiting code, but also for example CPFP combination code and RBF code). But it's really not as simple as making the index order configurable - sorry for saying so at first. |
Even in that case, both the "general reward" and the "general cost" indexes can use int64_t instead of CFeeRate and size_t respectively. Can we agree on that first? I still don't understand why this needs transaction replacement. We can add it or not as normal and, after adding, trim to the desired size. with this, we could have a unified index that it's just reward/cost instead of two separate ones.
|
@jtimon There is a DoS attack possible by mempool limiting, where someone sends a transaction that ends up at the bottom of the mempool, and then sends another transaction with slightly higher feerate, causing the previous one to be evicted later on. This leads to network broadcast bandwidth at much lower cost than the actual network relay fee, as discovered by @lapp0. The solution is to treat block size limiting as transaction replacement with the mempool bottom (sorted by feerate/score), and require that the new transaction pays in fee for the relay of the old transactions that we kicked out. |
@sipa You don't need to optimize a ratio, if you can represent both the rewards and costs in comparable units. Then you could optimize the difference. I wonder if unit costs could be represented in BTC/byte ... |
@dgenr8 Optimizing feerate is what you expect miners to do in their mempool, as it maximizes income given a constrained (by rule or propagation economics) block size. @jtimon Yes, I agree that instead of feerate and size we can use int64_t. Or double even. But the logic is already complicated enough here. I really don't think it's wise to spend more mental power of maintainers and reviewers to understand how this code will at some point generalize to a configurable policy. |
@sipa Miners don't care about relay cost, which you are now trying to include. |
@dgenr8 Of course. This is DoS protection code for relaying nodes, not for miners. Its primary purpose is preventing people from being able to spam the network, in various ways. It aims to build a mempool which is as aligned with miner's incentives as possible, but is restricted to prevent network actors from causing too high memory consumption, get massive flooding bandwidth or consume too much CPU power on the nodes traversed by it. |
Pushed a new version which tries more than just the bottom transactions and their dependencies in the mempool, is more efficient, and is better documented. |
Whatever, If |
@morcos In what I described, every child "pays" for its parents up-front in reduced mempool/relay attractiveness. Multiple children pay again for the same parent, and there is a recursive effect. Unconfirmed chains are expensive to process, have huge DoS risk, and limited usefulness. Replacement complicates everything. I mentioned in a mailing list post an overall approach I took. |
It seems everybody is happy with sipa@8adacf1 @morcos I'm not sure I understand your complains, but if the mempool is capped there must be some replacement criteria, even if it's the dumb "never replace" we have now (that's why I think the last commit would be clearer and more forward compatible with sipa@44d29ff ). In fact, capping the mempool with the current first seen replacement policy (that is, all replacements forbidden policy) would be the simplest way to cap the mempool (although not precisely the best way to cap it). Anything beyond that (always rejecting new transactions when the mempool is full) must necessarily be more complicated, but also hopefully better than never replacing. |
Btw, there's slightly related optimizations in #6445. The most relevant parts for this PR being in AcceptToMemoryPool:
|
Rebased version (with my suggestions on top) in https://github.com/jtimon/bitcoin/commits/post_limitpool |
Can this be rebased, the Qt keyword pull sneaked in here ;). |
|
||
totalTxSize -= it->GetTxSize(); | ||
cachedInnerUsage -= it->DynamicMemoryUsage(); | ||
mapTx.erase(it); |
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.
Shouldn't this be mapTx.erase(hash);
?
Greatly based on code from Pieter Wuille's bitcoin#6421
it++; | ||
continue; | ||
} | ||
if (CompareTxMemPoolEntryByFeeRate()(*it, toadd)) { |
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.
This test is better placed after we've randomly skipped some entries below. Otherwise we might end up evicting something that actually has a better fee rate than the tx being considered.
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.
Unless you're talking about an outer-loop transaction being hit which has an earlier (lower feerate) skipped transaction as dependency, I think the odds are small. But it won't hurt, both are very cheap checks.
Greatly based on code from Pieter Wuille's bitcoin#6421
Nodes can have divergent policies on which transactions they will accept and relay. This can cause you to repeatedly request and reject the same tx after its inved to you from various peers which have accepted it. Here we add rolling bloom filter to keep track of such rejections, clearing the filter every time the chain tip changes. Credit goes to Alex Morcos, who created the patch that this code is based on.
Indexes on: - Tx Hash - Fee Rate (fee-per-kb)
With some code taken from Pieter Wuille's bitcoin#6421
Superseded by a dozen other PRs. |
QT_NO_KEYWORDS prevents Qt from defining the `foreach`, `signals`, `slots` and `emit` macros. Avoid overlap between Qt macros and boost - for example #undef hackiness in bitcoin#6421.
This pull request contains a squashed version of #6331 and #6410, and replaces #6281.
Whenever a new transaction arrives, we check which of the lowest-feerate transactions in the mempool (including their dependencies) would have to be removed to make the memory consumption go below a configurable limit. The DoS protection relay rules then operate on the size of both the new transaction and the removed ones (as pointed out by @lapp0 in #6281).
Note that: