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.