-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
DoS protection: Weighted random drop of txs if mempool full #4145
Conversation
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 didn't do a full review, but this looks to be along the right lines.
This is the implementation for zcash/zips#275 |
Co-Authored-By: Daira Hopwood <daira@jacaranda.org>
I rewrote the representation of the collection of transactions/weights because I was concerned about performance when removing transactions from the mempool. In the first iteration we rebuild the complete list of weighted transactions each time a transaction was removed from the mempool. Now, we store the transactions in a tree and are able to add and remove transactions from the collection in logarithmic time. I also did some more cleanup and rebased on to master now that #4060 has been merged. |
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 don't understand some of the locking and also the considerations that go into the RecentlyEvictedList constants. Would be great if we can talk about those so that I can give this a better review.
src/mempoollimit.cpp
Outdated
pruneList(); | ||
if (txIdsAndTimes[txIdsAndTimesIndex].is_initialized()) { | ||
auto txIdAndTime = txIdsAndTimes[txIdsAndTimesIndex]; | ||
txIdSet.erase(txIdAndTime.get().first); |
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.
So.. we just overwrite anything existing in there? This seems like someone could fill up the RecentlyEvictedList with permutations of (malleable) transactions and we still end up in DoS. What have I missed?
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.
Note that the RecentlyEvictedList
is not intended to mitigate intentional DoS.
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.
It's a ring buffer, so by design it eventually overwrites itself if it is being filled more quickly than the entries expire. RecentlyEvictedList
clearly can't be an unbounded buffer, or it would itself be a DoS vector. The question is how large the buffer's bound should be.
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.
We could make the size of the recently evicted list configurable, but I am not sure about how beneficial that would be.
src/mempoollimit.h
Outdated
#include "uint256.h" | ||
|
||
const size_t DEFAULT_MEMPOOL_TOTAL_WEIGHT_LIMIT = 80000000; | ||
const int64_t DEFAULT_MEMPOOL_EVICTION_MEMORY_MINUTES = 60; |
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 don't understand the considerations that went into this figure.
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.
They're described in the draft ZIP.
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.
Just did a brief first pass...
I think there need to be more comments explaining the purpose of each piece of the API and the algorithms. As it stands I have to reverse engineer the intentions behind much of the code and understand each edge case you are addressing.
src/mempoollimit.cpp
Outdated
void WeightedTxTree::remove(const uint256& txId) | ||
{ | ||
if (txIdToIndexMap.find(txId) == txIdToIndexMap.end()) { | ||
return; |
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 should never happen anyway, right?
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.
In testing I found that this does actually happen - somewhere remove is being called on transactions that have already been removed. I did not research where or why, but as it stands this is necessary.
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.
Please file a ticket to investigate this.
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 created #4159, but I question how much time it is worth spending on this.
src/mempoollimit.cpp
Outdated
WeightedTxInfo WeightedTxInfo::from(const CTransaction& tx, const CAmount& fee) | ||
{ | ||
size_t memUsage = RecursiveDynamicUsage(tx); | ||
memUsage += tx.vJoinSplit.size() * JOINSPLIT_SIZE; |
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.
Should these instead be folded into RecursiveDynamicUsage
?
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.
Yes, they should be (this is another example of inherited logic we've not fully integrated Zcash changes into).
Also, JOINSPLIT_SIZE
now depends on the network upgrade level (which means that we computing it, we should amend the computation to trigger the post-Sapling serialization logic in the same way we amend the logic in the transaction serialization itself).
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.
Should this be fixed as part of this issue, or can it be done as follow up?
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 did this as follow up, we could also incorporate this in to z_mergetoaddress
which does its own tx size estimation.
src/mempoollimit.cpp
Outdated
WeightedTxInfo WeightedTxInfo::from(const CTransaction& tx, const CAmount& fee) | ||
{ | ||
size_t memUsage = RecursiveDynamicUsage(tx); | ||
memUsage += tx.vJoinSplit.size() * JOINSPLIT_SIZE; |
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.
Yes, they should be (this is another example of inherited logic we've not fully integrated Zcash changes into).
Also, JOINSPLIT_SIZE
now depends on the network upgrade level (which means that we computing it, we should amend the computation to trigger the post-Sapling serialization logic in the same way we amend the logic in the transaction serialization itself).
src/mempoollimit.cpp
Outdated
pruneList(); | ||
if (txIdsAndTimes[txIdsAndTimesIndex].is_initialized()) { | ||
auto txIdAndTime = txIdsAndTimes[txIdsAndTimesIndex]; | ||
txIdSet.erase(txIdAndTime.get().first); |
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.
It's a ring buffer, so by design it eventually overwrites itself if it is being filled more quickly than the entries expire. RecentlyEvictedList
clearly can't be an unbounded buffer, or it would itself be a DoS vector. The question is how large the buffer's bound should be.
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.
reACK modulo some minor comments, and the issue about parameter names relative to the ZIP. Nice doc improvements.
@zkbot try |
DoS protection: Weighted random drop of txs if mempool full
💔 Test failed - pr-try |
Intermittent failures. |
src/init.cpp
Outdated
strUsage += HelpMessageOpt("-mempoolevictionmemoryminutes=<n>", strprintf(_("The number of minutes before allowing rejected transactions to re-enter the mempool. (default: %u)"), DEFAULT_MEMPOOL_EVICTION_MEMORY_MINUTES)); | ||
strUsage += HelpMessageOpt("-mempooltotalcostlimit=<n>",strprintf(_("An upper bound on the maximum size in bytes of all transactions in the mempool. (default: %s)"), DEFAULT_MEMPOOL_TOTAL_COST_LIMIT)); | ||
strUsage += HelpMessageOpt("-mempool.eviction_memory_minutes=<n>", strprintf(_("The number of minutes before allowing rejected transactions to re-enter the mempool. (default: %u)"), DEFAULT_MEMPOOL_EVICTION_MEMORY_MINUTES)); | ||
strUsage += HelpMessageOpt("-mempool.tx_cost_limit=<n>",strprintf(_("An upper bound on the maximum size in bytes of all transactions in the mempool. (default: %s)"), DEFAULT_MEMPOOL_TOTAL_COST_LIMIT)); |
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.
Sorry, but I think the old way is better, because, as Eirik suspected, .
does seem to have a special meaning at least in Bitcoin:
https://github.com/bitcoin/bitcoin/blob/b6e34afe9735faf97d6be7a90fafd33ec18c0cbb/src/util/system.cpp#L291
https://github.com/bitcoin/bitcoin/blob/b6e34afe9735faf97d6be7a90fafd33ec18c0cbb/src/util/system.cpp#L439
https://github.com/bitcoin/bitcoin/blob/b6e34afe9735faf97d6be7a90fafd33ec18c0cbb/src/util/system.cpp#L827
I think this relates to "sections" of the configuration file. We haven't merged that yet, but we could someday.
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 removed the .
and _
.
@zkbot r+ |
📌 Commit 40a7156 has been approved by |
⌛ Testing commit 40a7156 with merge 0b0c724cde98061fe98ab5acf543f9dcef0d45cb... |
💔 Test failed - pr-merge |
Passed on some workers but not others, looks like transient failures.
I am less familiar with the above transient failure, but those tests pass for me locally. |
@zkbot retry |
DoS protection: Weighted random drop of txs if mempool full
Merging this broke the compilation on macOS #4164 |
No description provided.