-
Notifications
You must be signed in to change notification settings - Fork 38.8k
Keep track of recently rejected transactions with a rolling bloom filter #6452
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
Conversation
305e61b to
5250f5f
Compare
|
Untested ACK. I was thinking about implementing something like this myself while working on the mempool limitation. Given the false positive rate and the amount of memory usage, I think this approach is better than #6450. |
|
If I've done the math right, I think it would only take about a million rejects to push the false positive rate on this near 98%. At 1000 txn/sec, this is somewhere on the order of 20 minutes. It only takes a few hundred thousand to push it into low, yet still problematic double digit false positive territory. I also don't fully understand the comment in the code that claims this uses 1.7MB. I calculated the filter size myself using the parameters from the code and I get about 430k each, for around 960k total. It's worth noting that the attack surface here involves filling up the bloom filter and causing a high false positive rate which can blind nodes to incoming transactions. If a bloom filter is used here, I think it needs to be larger. (Maybe that was the intention?) Thoughts? |
|
Ahh, I see nevermind. I didn't catch that the rolling stuff allocates filters at 2x size, and rotates them when they're half full making the fprate never go above the specified value (1e-6). The sizes add up now too... Looks good. |
src/main.cpp
Outdated
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.
Not sure if this should be insecure_rand. insecure_rand is meant for high-performance low-security use.
- No performance constraints: this is done once, at initialization time
- If this value could be guessed by an adversary, there is additional DoS risk.
- Also, the spread between nodes should be as uniform as possible to make sure that if false positives happen, they are at least as non-consistent as possible.
So IMO let's use the normal GetRand.
What I am also slightly worried about here is the static global initialization. Random generators will not have been initialized at that time.
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.
Changed to GetRand()
The other CRollingBloom filter use is in net.cpp, which also uses insecure_rand() - might be worth it to fix that too.
Re: static global initialization, it'd be possible to change the seed here every time the bloom filter is cleared; dunno what's the best way to actually do that in C++ though.
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.
GetRand() relies on openssl being initialized, so something very bad could happen if things are executed in the wrong order.
Thus I'd prefer an explicit initialization. Possibly make recentRejects a pointer and explicitly allocate/deallocate it.
Setting a new tweak every time the filter is cleared may be overkill, I don't know.
Agree re: net.cpp, although that structure is per-peer so there's not as much scope for monkey tricks.
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.
At what point can we actually guarantee OpenSSL is initialized? Remember that the capcity of the filter has to be pretty large - 120,000 transactions - so it could take a long time before it gets rolled over if a bad initialization is an issue.
Regardless of how it gets initialized, only 1/1,000,000 txs will be affected. Sure an attacker might be able to pick-and-choose that subset, but I can't think of any example other than zeroconf crap where that actually matters.
|
utACK apart from randomness nit. Edit: as using randomness during static initialization seems error prone. |
3cff511 to
fb09181
Compare
|
@laanwj As per @sipa's suggestion I added a reset() method that resets nTweak as well, and went even further to change the CRollingBloomFilter to handle setting nTweak for you. This is a bigger patch that changes addrKnown a bit as well, but overall this should simplify all uses by ensuring the default behavior is a secure RNG source. |
c3ae483 to
767e536
Compare
src/main.cpp
Outdated
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 realize you changed this. For whatever reason the previous behavior was to relay transactions we rejected (even for reasons other than we already had them) if we received them from a white listed peer. It might be better to leave this unchanged. Although orphan transactions are already not relayed, so its not quite consistent as is.
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.
Given that relaying invalid txs can get you banned, seems like a vulnerability waiting to be exploited... Sure this is for whitelisted peers only, but there's many shades of grey in white. :)
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 there are some companies that rely on the current behavior that peers that have you whitelisted relay all your transactions. E.g. they use a bitcoin node as border router.
Agree that it's a grey area, but changing this without announcement is a bad idea.
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 the behaviour should be to relay every valid transaction received from a whitelisted peer (including when it was in the mempool already).
I don't think we should relay invalid transactions from whitelisted peers (especially not things tha would otherwise trigger DoS score), but instead yell loudly that some whitelisted peer is broken.
Concept ACK here, though.
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.
Fair enough - reverted and added a FIXME for a future patch to fix.
767e536 to
65c4c69
Compare
|
ACK |
While CBloomFilter is usually used with an explicitly set nTweak, CRollingBloomFilter is only used internally. Requiring every caller to set nTweak is error-prone and redundant; better to have the class handle that for you with a high-quality randomness source. Additionally when clearing the filter it makes sense to change nTweak as well to recover from a bad setting, e.g. due to insufficient randomness at initialization, so the clear() method is replaced by a reset() method that sets a new, random, nTweak value.
|
Code review ACK on sipa's changes |
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. Original code by Peter Todd. Refactored to not construct the filter at startup time by Pieter Wuille.
65c4c69 to
0847d9c
Compare
|
ACK sipa's changes as well, updated this pull-req to them. I'll note there is a chance that petertodd@d741371 could lead to intermittent unit test failures. |
|
I still don't understand why can't we reuse the same cache for rejections and #6077 (what I wanted to do in jtimon@935ee1e to make 0-size mempools safe). |
|
Error in Travis during the Python unit tests:
The above failure does not seem related to the value of the tweak. Edit: I can reproduce the same problem locally, consistently, when I start e.g. Either
|
|
This can be solved by moving // Initialize global variables that cannot be constructed at startup.
recentRejects.reset(new CRollingBloomFilter(120000, 0.000001));
Edit: Hm no... that doesn't do it either. It needs to go all the way to the beginning of the function, after locking cs_main. |
|
Continued in #6498 |
|
edit: moved comments to #6498 |
@Marcos's #6450, but with a rolling bloom filter instead of a limitedmap. The number of rejected txids saved has been greatly improved, which should help in some attack scenarios. In addition the filter is now cleared every time the chaintip changes, rather than based on a timeout, to better match the cases where a previously rejected transaction would now be valid. (nLockTime, double-spends, etc.)