-
Notifications
You must be signed in to change notification settings - Fork 36.3k
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
p2p: Proof-of-concept: Improve DoS-resistance to low-work headers chains #17332
p2p: Proof-of-concept: Improve DoS-resistance to low-work headers chains #17332
Conversation
A low-work headers chain might be valid according to consensus rules, yet uninteresting for reaching consensus, because of the little work on the chain. Currently, a low-work headers chain that is received by bitcoind is nevertheless stored in memory (permanently), because the headers download logic stores valid headers as it goes, and we only look at the total work on the chain at a later point. By definition, a low-work headers chain can be cheap to produce, so the cost to an adversary for performing a memory DoS (on the entire network of reachable nodes) is not very high. Ideally, the cost to making a node store a headers chain should be related to the total work on the best chain, as we're only ever interested in the most work chain for consensus purposes. (If an adversary is able to perform a memory DoS by producing a headers chain with work comparable to the work on the best chain, there's not much we could do about it, as a node must be aware of all headers chains that potentially have the most work in order to remain in consensus. So requiring that a headers chain have work comparable to the most-work chain before we store it is essentially the best we can do.) This patch introduces a headers download scheme that attempts to verify that a peer's headers chain has sufficient work (namely, within a week of our current tip and at least as much work as nMinimumChainWork) before committing to permanent storage. If a peer gives us a headers message whose last header has less work than our anti-DoS threshold, then we store those headers in memory that is allocated to just that peer, until we've seen a chain tip building on those headers that has sufficient work. At that point, the headers will be processed and stored globally. Because of the time-warp problem, where a chain producing blocks at a rate of 6 blocks/second can theoretically be valid even while being low-work, we have to consider the possibility of being fed a very long, low-work chain. If we stored all of a peer's headers even in temporary memory, this could be enough to be a memory DoS by itself. To address this, this patch uses a heuristic to cache just the last header in each message if the time on the chain is progressing slower than expected. If the chain ends up having sufficient work, then we can redownload the chain, verifying that we get the same headers back by using these cached headers as intermediate markers that must match the redownloaded chain. Using this scheme, we can bound the memory used for headers download by a single peer to roughly the amount of memory we'd expect an "honest" chain to use (1 block header per 10 minutes starting at the genesis block). To prevent an adversary from using many inbound peers to flood a node's memory due to simultaneous headers sync, this patch also includes logic to restrict using the per-peer headers sync memory by more than one peer at a time, along with timeout logic to prevent a single peer from starving headers sync from other peers.
Please see My goal with this patch is to demonstrate that it should be possible to The algorithm is described a bit in the OP and further in comments in (a) We reuse the "headers" message in many contexts (it can be a response to a (b) Because our p2p protocol does not support downloading headers in reverse, In the absence of an attacker giving a node a "fake" chain, this code would That said, this patch is very complex, and I am not sure it's worth the review One alternate idea for increasing the cost of attack, which would be vastly (a) The cost of an attack still does not go up as the chain work goes up (so in (b) Changing the consensus rules we enforce comes with a higher bar for If this high level approach (of having peers prove that their headers chains |
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ConflictsReviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. |
this would be so much cleaner and simpler if the p2p protocol had explicit measures for reverse fetching... ( https://en.bitcoin.it/w/index.php?title=User:Gmaxwell/Reverse_header-fetching_sync&action=history ) |
Cool, I had heard of the mitigation a long time ago but hadn't seen it written anywhere(hadn't seen bitcointalk thread either). |
Too bad that this requires so much additional code and modifications to inner critical logic. Also, it doesn't compile on any of the ci builds. |
d6e64dc
to
06da4d6
Compare
A low-work headers chain might be valid according to consensus rules, yet
uninteresting for reaching consensus, because of the little work on the chain.
Currently, a low-work headers chain that is received by bitcoind is
nevertheless stored in memory (permanently), because the headers download logic
stores valid headers as it goes, and we only look at the total work on the
chain at a later point.
By definition, a low-work headers chain can be cheap to produce, so the cost to
an adversary for performing a memory DoS (on the entire network of reachable
nodes) is not very high (checkpoints currently make this cost non-trivial, but the cost is not increasing as the chain advances).
Ideally, the cost to making a node store a headers chain should be related to
the total work on the best chain, as we're only ever interested in the most
work chain for consensus purposes. (If an adversary is able to perform a memory
DoS by producing a headers chain with work comparable to the work on the best
chain, there's not much we could do about it, as a node must be aware of all
headers chains that potentially have the most work in order to remain in
consensus. So requiring that a headers chain have work comparable to the
most-work chain before we store it is essentially the best we can do.)
This patch introduces a headers download scheme that attempts to verify that a
peer's headers chain has sufficient work (namely, within a week of our current
tip and at least as much work as nMinimumChainWork) before committing to
permanent storage.
If a peer gives us a headers message whose last header has less work than our
anti-DoS threshold, then we store those headers in memory that is allocated to
just that peer, until we've seen a chain tip building on those headers that has
sufficient work. At that point, the headers will be processed and stored
globally.
Because of the time-warp problem, where a chain producing blocks at a rate of 6
blocks/second can theoretically be valid even while being low-work, we have to
consider the possibility of being fed a very long, low-work chain. If we stored
all of a peer's headers even in temporary memory, this could be enough to be a
memory DoS by itself. To address this, this patch uses a heuristic to cache
just the last header in each message if the time on the chain is progressing
slower than expected. If the chain ends up having sufficient work, then we
can redownload the chain, verifying that we get the same headers back by using
these cached headers as intermediate markers that must match the redownloaded
chain.
Using this scheme, we can bound the memory used for headers download by a
single peer to roughly the amount of memory we'd expect an "honest" chain to
use (1 block header per 10 minutes starting at the genesis block). To prevent
an adversary from using many inbound peers to flood a node's memory due to
simultaneous headers sync, this patch also includes logic to restrict using the
per-peer headers sync memory by more than one peer at a time, along with timeout
logic to prevent a single peer from starving headers sync from other peers.