Skip to content
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

Merged
merged 29 commits into from
Oct 21, 2019

Conversation

Eirik0
Copy link
Contributor

@Eirik0 Eirik0 commented Sep 26, 2019

No description provided.

@Eirik0 Eirik0 added this to Needs Prioritization in Arborist Team via automation Sep 26, 2019
@Eirik0 Eirik0 added this to the v2.1.0 milestone Sep 26, 2019
@Eirik0 Eirik0 requested a review from ebfull September 26, 2019 16:27
@Eirik0 Eirik0 moved this from Needs Prioritization to Security Issue Backlog in Arborist Team Sep 26, 2019
@Eirik0 Eirik0 moved this from Security Issue Backlog to In Progress in Arborist Team Sep 26, 2019
@Eirik0 Eirik0 added the I-SECURITY Problems and improvements related to security. label Sep 26, 2019
Copy link
Contributor

@daira daira left a 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.

qa/rpc-tests/mempool_limit.py Outdated Show resolved Hide resolved
qa/rpc-tests/mempool_limit.py Outdated Show resolved Hide resolved
qa/rpc-tests/mempool_limit.py Outdated Show resolved Hide resolved
qa/rpc-tests/mempool_limit.py Outdated Show resolved Hide resolved
src/gtest/test_mempoollimit.cpp Outdated Show resolved Hide resolved
src/gtest/test_mempoollimit.cpp Outdated Show resolved Hide resolved
src/gtest/test_mempoollimit.cpp Show resolved Hide resolved
src/init.cpp Outdated Show resolved Hide resolved
@Eirik0 Eirik0 changed the title [WIP] DoS protection: Weighted random drop of txs if mempool full DoS protection: Weighted random drop of txs if mempool full Sep 27, 2019
@Eirik0 Eirik0 moved this from In Progress to In Review in Arborist Team Sep 27, 2019
@Eirik0
Copy link
Contributor Author

Eirik0 commented Sep 27, 2019

This is the implementation for zcash/zips#275

src/init.cpp Outdated Show resolved Hide resolved
src/init.cpp Outdated Show resolved Hide resolved
src/init.cpp Outdated Show resolved Hide resolved
src/init.cpp Outdated Show resolved Hide resolved
src/Makefile.am Outdated Show resolved Hide resolved
src/txmempool.cpp Outdated Show resolved Hide resolved
src/init.cpp Outdated Show resolved Hide resolved
src/main.cpp Outdated Show resolved Hide resolved
src/txmempool.cpp Outdated Show resolved Hide resolved
src/txmempool.cpp Outdated Show resolved Hide resolved
@Eirik0
Copy link
Contributor Author

Eirik0 commented Oct 7, 2019

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.

@Eirik0 Eirik0 requested review from str4d and zebambam October 7, 2019 20:49
Copy link

@zebambam zebambam left a 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 Show resolved Hide resolved
pruneList();
if (txIdsAndTimes[txIdsAndTimesIndex].is_initialized()) {
auto txIdAndTime = txIdsAndTimes[txIdsAndTimesIndex];
txIdSet.erase(txIdAndTime.get().first);
Copy link

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?

Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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.

#include "uint256.h"

const size_t DEFAULT_MEMPOOL_TOTAL_WEIGHT_LIMIT = 80000000;
const int64_t DEFAULT_MEMPOOL_EVICTION_MEMORY_MINUTES = 60;
Copy link

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.

Copy link
Contributor

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.

src/mempoollimit.h Outdated Show resolved Hide resolved
src/mempoollimit.h Outdated Show resolved Hide resolved
Copy link
Contributor

@ebfull ebfull left a 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.

void WeightedTxTree::remove(const uint256& txId)
{
if (txIdToIndexMap.find(txId) == txIdToIndexMap.end()) {
return;
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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 Show resolved Hide resolved
src/mempoollimit.cpp Outdated Show resolved Hide resolved
WeightedTxInfo WeightedTxInfo::from(const CTransaction& tx, const CAmount& fee)
{
size_t memUsage = RecursiveDynamicUsage(tx);
memUsage += tx.vJoinSplit.size() * JOINSPLIT_SIZE;
Copy link
Contributor

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?

Copy link
Contributor

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).

Copy link
Contributor Author

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?

Copy link
Contributor Author

@Eirik0 Eirik0 Oct 9, 2019

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 Show resolved Hide resolved
src/mempoollimit.cpp Outdated Show resolved Hide resolved
qa/rpc-tests/mempool_limit.py Outdated Show resolved Hide resolved
qa/rpc-tests/mempool_limit.py Outdated Show resolved Hide resolved
qa/rpc-tests/mempool_limit.py Outdated Show resolved Hide resolved
qa/rpc-tests/mempool_limit.py Show resolved Hide resolved
qa/rpc-tests/mempool_limit.py Outdated Show resolved Hide resolved
src/main.cpp Outdated Show resolved Hide resolved
src/gtest/test_mempoollimit.cpp Outdated Show resolved Hide resolved
src/txmempool.h Outdated Show resolved Hide resolved
src/init.cpp Outdated Show resolved Hide resolved
src/txmempool.h Outdated Show resolved Hide resolved
src/mempoollimit.h Outdated Show resolved Hide resolved
src/mempoollimit.cpp Outdated Show resolved Hide resolved
src/mempoollimit.cpp Outdated Show resolved Hide resolved
src/mempoollimit.h Outdated Show resolved Hide resolved
WeightedTxInfo WeightedTxInfo::from(const CTransaction& tx, const CAmount& fee)
{
size_t memUsage = RecursiveDynamicUsage(tx);
memUsage += tx.vJoinSplit.size() * JOINSPLIT_SIZE;
Copy link
Contributor

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 Show resolved Hide resolved
pruneList();
if (txIdsAndTimes[txIdsAndTimesIndex].is_initialized()) {
auto txIdAndTime = txIdsAndTimes[txIdsAndTimesIndex];
txIdSet.erase(txIdAndTime.get().first);
Copy link
Contributor

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.

src/mempool_limit.h Outdated Show resolved Hide resolved
src/mempool_limit.h Outdated Show resolved Hide resolved
Copy link
Contributor

@daira daira left a 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.

@Eirik0
Copy link
Contributor Author

Eirik0 commented Oct 18, 2019

@zkbot try

@zkbot
Copy link
Contributor

zkbot commented Oct 18, 2019

⌛ Trying commit e744cec with merge 5c0b3a6...

zkbot added a commit that referenced this pull request Oct 18, 2019
DoS protection: Weighted random drop of txs if mempool full
@zkbot
Copy link
Contributor

zkbot commented Oct 18, 2019

💔 Test failed - pr-try

@Eirik0
Copy link
Contributor Author

Eirik0 commented Oct 18, 2019

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));
Copy link
Collaborator

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the . and _.

@Eirik0
Copy link
Contributor Author

Eirik0 commented Oct 21, 2019

@zkbot r+

@zkbot
Copy link
Contributor

zkbot commented Oct 21, 2019

📌 Commit 40a7156 has been approved by Eirik0

@zkbot
Copy link
Contributor

zkbot commented Oct 21, 2019

⌛ Testing commit 40a7156 with merge 0b0c724cde98061fe98ab5acf543f9dcef0d45cb...

@zkbot
Copy link
Contributor

zkbot commented Oct 21, 2019

💔 Test failed - pr-merge

@Eirik0
Copy link
Contributor Author

Eirik0 commented Oct 21, 2019

Passed on some workers but not others, looks like transient failures.

Running 310 test cases...
0%   10   20   30   40   50   60   70   80   90   100%
|----|----|----|----|----|----|----|----|----|----|
***************************�[0;39;49
m************
********test/rpc_wallet_tests.cpp(294): error: in "rpc_wallet_tests/rpc_wallet": unexpected exception thrown by CallRPC("getblock 0")
test/rpc_wallet_tests.cpp(295): error: in "rpc_wallet_tests/rpc_wallet": unexpected exception thrown by CallRPC("getblock 0 0")
test/rpc_wallet_tests.cpp(296): error: in "rpc_wallet_tests/rpc_wallet": unexpected exception thrown by CallRPC("getblock 0 1")
test/rpc_wallet_tests.cpp(297): error: in "rpc_wallet_tests/rpc_wallet": unexpected exception thrown by CallRPC("getblock 0 2")
****
*** 4 failures are detected in the test module "Bitcoin Test Suite"

I am less familiar with the above transient failure, but those tests pass for me locally.

@Eirik0
Copy link
Contributor Author

Eirik0 commented Oct 21, 2019

@zkbot retry

@zkbot
Copy link
Contributor

zkbot commented Oct 21, 2019

⌛ Testing commit 40a7156 with merge fffd5da...

zkbot added a commit that referenced this pull request Oct 21, 2019
DoS protection: Weighted random drop of txs if mempool full
@zkbot
Copy link
Contributor

zkbot commented Oct 21, 2019

☀️ Test successful - pr-merge
Approved by: Eirik0
Pushing fffd5da to master...

@zkbot zkbot merged commit 40a7156 into zcash:master Oct 21, 2019
Arborist Team automation moved this from In Review to Released (Merged in Master) Oct 21, 2019
@Eirik0 Eirik0 mentioned this pull request Oct 21, 2019
@rex4539
Copy link
Contributor

rex4539 commented Oct 22, 2019

Merging this broke the compilation on macOS #4164

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
I-SECURITY Problems and improvements related to security.
Projects
Arborist Team
  
Released (Merged in Master)
Development

Successfully merging this pull request may close these issues.

None yet

8 participants