<blockquote>
<p>since checkpoints are removed, we would be vulnerable to this attack</p>
</blockquote>
<p>Where did you see that checkpoints are removed?  They're still in <code>src/chainparams.cpp</code> as of current master (commit <code>f8bcef38f</code>).</p>
<blockquote>
<p>How does the headers-first synchronization prevent the fill-disk attack?</p>
</blockquote>
<p>Headers-first prevents a related attack where the client would accept &quot;orphan&quot; blocks---block with no known parent---and store them until their parent was received.  This prevented wasted bandwidth and sped up validation in the normal case but made disk-fill attacks easier in the pathological case.</p>
<p><a href="https://developer.bitcoin.org/_images/en-orphan-stale-definition.svg" rel="nofollow noreferrer"><img src="https://developer.bitcoin.org/_images/en-orphan-stale-definition.svg" alt="Orphan versus Stale" /></a></p>
<p>Headers first allows Bitcoin Core to discover the most proof of work block chain (headers chain) that its peers know about before downloading any blocks, which allows it to ensure any blocks it receives are on that chain.  This, in turn, means that it never needs to download or store orphan blocks.</p>
<blockquote>
<p>A syncing node downloads all headers from a single peer. If that peer only sends headers from its malicious low-difficulty branch, won't the syncing node try to download all those blocks, and get it's disk filled?</p>
</blockquote>
<p>That's correct, and that's why checkpoints are still used in the code as far as I know.  My understanding is that, for the ultimate removal of checkpoints, three things were desired:</p>
<ul>
<li><p>Minimum chainwork: a feature coded into a node that tells it the legitimate chain must have at least X amount of chainwork, with X being set to the value for a recent block near the time of a software release.  This replaces the original use of checkpoints in preventing network-level attackers from feeding clients long, low-PoW chains containing valid blocks but which aren't the consensus best block chain.  This was deployed in Bicoin Core 0.13.2.</p>
</li>
<li><p>Assumed Valid Blocks: a feature designed to replace the secondary use of checkpoints for (optionally) speeding up Initial Block Download (IBD) by skipping validation of signatures in old blocks.  This was deployed in Bitcoin Core 0.14</p>
</li>
<li><p>A minimum difficult soft fork: a change to directly address the block-fill (or header-fill) attack you described by raising the minimum difficulty at various epochs in the block chain to correspond roughly with the actual observed increases in difficulty.  This would make it more expensive for an attacker to feed fake blocks to a node.  To the best of my knowledge this has not yet reached the BIP stage and I'm not sure it's currently being actively championed.</p>
</li>
</ul>
<p>For reference, this topic was discussed in the 2017-03-02 Bitcoin Core developer meeting: <a href="https://bitcoincore.org/en/meetings/2017/03/02/#discussion" rel="nofollow noreferrer">https://bitcoincore.org/en/meetings/2017/03/02/#discussion</a></p>

CC BY-SA 4.0
2018-06-07T12:33:57.150Z
Yes, seems I was wrong in assuming that the old checkpoints had been removed. So without the minimum difficulty soft fork, we're still vulnerable to the disk-fill attack from the last checkpoint, which is at height 295000. The difficulty has increased about 1000x since then, so the attack may be feasible.

I also see a line 
'
        // The best chain should have at least this much work.
        consensus.nMinimumChainWork = uint256S("0x000000000000000000000000000000000000000000f91c579d57cad4bc5278cc");
'
Which seems to require any chain to have a lot of PoW to be considered ok.
Kalle Rosenbaum
CC BY-SA 4.0
2018-06-07T12:44:35.597Z
I see there's a `nMinimumChainWork` parameter, that's used in https://github.com/bitcoin/bitcoin/blob/f8bcef38fb9be48f0f5908a6c4c0cbe8c5a729d6/src/net_processing.cpp#L1511. That should cover our asses?
Kalle Rosenbaum
CC BY-SA 4.0
2018-06-07T12:50:34.857Z
AFAIK, that doesn't prevent header-fill attacks.  An exclusive peer can still feed you a header chain with 5 trillion diff-1 headers for about the same cost of producing a block at the tip.  80 bytes per header times 5 trillion is 400 terabytes, though probably something else breaks before then (like block header time rolls over).
David A. Harding
CC BY-SA 4.0
2018-06-07T13:06:47.283Z
Right. But let's see how many headers we need before consensus rules break. We need montonically increasing MTP. which means that the block timestamps need to increase at least one second per block, right? So we can't have move block headers than there are seconds since genesis block (+/- a few hours). That's about 315,360,000 blocks. 315 million block headers is "just" 25GB. That's fine, since the node should have 200 GB to spare at header sync. This of course relies on 1) Monotone MTP is verified on header sync and 2) Blocks from the future are not allowed during header sync.
Kalle Rosenbaum
CC BY-SA 4.0
2018-06-07T15:37:09.630Z
@Kalle: an attacker can produce multiple low-difficulty branches if there was no protection against flooding by low-difficulty headers. Also, blocks only need a timestamp that is strictly larger than the median of the past 11 ones - that translates to incrementing the timestamp every 6 blocks AFAIK.
Pieter Wuille
CC BY-SA 4.0
2018-06-08T08:06:11.363Z
Summarizing the comments gathered: You can't quickly produce a long branch of low-difficulty headers from the last checkpoint with minimal increasing timestamp, because that would force your difficulty to go up. You're better off producing many 1-block branches from last checkpoint. The current difficulty is 1000x that of the last checkpoint, so if the whole network's hash rate cooperated, they couldn't currently produce more than ~6000*80=480kB of headers per hour. I think I can relax for now. Thanks for clarifications @Pieter, I should've consulted my own book before posting about MTP :-)
Kalle Rosenbaum
CC BY-SA 4.0
2023-12-31T14:53:45.583Z
This answer is outdated since the introduction of headers pre-syncing in Bitcoin Core 24.0 (PR 25717).
Pieter Wuille
CC BY-SA 4.0
2018-06-07T12:15:55.297Z
2023-01-23T22:40:50.053Z
2023-01-23T22:40:50.053Z
David A. Harding
<p>To add to the other answer, since Bitcoin Core 24.0, there is an additional protection implemented against low-difficulty header spam: <strong>header pre-syncing</strong>.</p>
<p>To recapitulate, since the <a href="https://github.com/bitcoin/bitcoin/pull/4468" rel="nofollow noreferrer">headers-first synchronization</a> introduced in Bitcoin Core 0.10.0, blocks are never downloaded before their headers are known and verified to have sufficient work (which means: enough to within one day of the active chain tip, and more than the preconfigured minimum chain work). This means we already don't need to worry about low-difficulty <em>block</em> spam anymore. The blocks are just not downloaded unless they're part of a chain that's proven to be good enough.</p>
<p>Yet, a weaker problem remained: a peer could start giving us (multiple) chains of headers that never amount to anything valuable. Since the headers are sent in forward order, there is no way to know at the beginning how good the result will get. The old solution (checkpoints) is unsatisfactory: it relies on updated software with hardcoded overrides on what chain is acceptable. As of Bitcoin Core 26.0, <a href="https://github.com/bitcoin/bitcoin/blob/v26.0/src/kernel/chainparams.cpp#L157L173" rel="nofollow noreferrer">the checkpoints remain</a>, but haven't been updated since 2014. By now, mining has become so much cheaper per hash, that an attacker could realistically start an attack <em>after</em> the last checkpoint.</p>
<p>Since Bitcoin Core 24.0, a new solution has been implemented: <a href="https://github.com/bitcoin/bitcoin/pull/25717" rel="nofollow noreferrer">headers pre-syncing</a>.</p>
<p>The idea is that the header synchronization (which precedes the block synchronization) is split up in two phases:</p>
<ul>
<li>In a first phase, <em>header presyncing</em>, headers from a peer are downloaded and verified, but <strong>not stored</strong>, because we don't yet know whether they'll end up being good enough. Instead, only a very compact (salted) hash of these headers is kept (in per-peer memory, discarded upon disconnect).</li>
<li>In a second phase, <em>header redownloading</em>, the same headers are downloaded again from the same peer, and compared to the stored hash(*). If there is a match, they're fed to full header validation, which stores them, and will trigger block download.</li>
</ul>
<p>This approach comes at a cost - the header synchronization is effectively performed twice, doubling its bandwidth cost (which is still small compared to full block download), but removes the last reliance on checkpoints in the codebase. They'll likely be removed in some future version.</p>
<p>(*) A simple hash wouldn't be sufficient, as we need the ability to verify headers along the way, not just the end. The actual structure consists of a single 1-bit salted hash every ~600 blocks. To compensate for the (extremely) small hash, upon redownloading, there is a buffer of ~14000 headers that are downloaded before validation. Only if all the ~23 1-bit hashes in those 14000 headers match, then the beginning of the buffer is fed to validation. This means every header has some 23 bits checked against it before validation, which would cost an attacker millions of tries to succeed against.</p>

CC BY-SA 4.0
2024-01-01T03:54:09.627Z
2024-01-08T03:00:05.000Z
2024-01-08T03:00:05.000Z
Pieter Wuille
<p>Checkpoints prevented an attack where a node could mine many many low-difficulty blocks from an early point in the blockchain and serve those blocks to syncing nodes who would see their disks fill up. This is described in <a href="https://en.bitcoin.it/wiki/Bitcoin_Core_0.11_(ch_5):_Initial_Block_Download#Checkpoints" rel="nofollow noreferrer">https://en.bitcoin.it/wiki/Bitcoin_Core_0.11_(ch_5):_Initial_Block_Download#Checkpoints</a>.</p>
<p>Now since checkpoints are removed, we would be vulnerable to this attack. But it is stated in (<a href="https://bitcoin.stackexchange.com/a/75735/69518">https://bitcoin.stackexchange.com/a/75735/69518</a>) that this is prevented by the newish headers-first synchronization mechanism.</p>
<p>How does the headers-first synchronization prevent the disk-fill attack?</p>
<p>A syncing node downloads all headers from a single peer. If that peer only sends headers from its malicious low-difficulty branch, won't the syncing node try to download all those blocks, and get it's disk filled?</p>

<bitcoin-core><synchronization><attack><block-header>
How does headers-first prevent disk-fill attack?
Kalle Rosenbaum
# How does headers-first prevent disk-fill attack?

(Asked by: username_0 on 2018-06-07)

Checkpoints prevented an attack where a node could mine many many low-difficulty blocks from an early point in the blockchain and serve those blocks to syncing nodes who would see their disks fill up. This is described in <https://en.bitcoin.it/wiki/Bitcoin_Core_0.11_(ch_5):_Initial_Block_Download#Checkpoints>.

Now since checkpoints are removed, we would be vulnerable to this attack. But it is stated in (<https://bitcoin.stackexchange.com/a/75735/69518>) that this is prevented by the newish headers-first synchronization mechanism.

How does the headers-first synchronization prevent the disk-fill attack?

A syncing node downloads all headers from a single peer. If that peer only sends headers from its malicious low-difficulty branch, won't the syncing node try to download all those blocks, and get it's disk filled?

**Tags:** bitcoin-core, synchronization, attack, block-header


---

## 2 answers

### Answer by username_2 on 2024-01-01. Score: 9 | Accepted answer

To add to the other answer, since Bitcoin Core 24.0, there is an additional protection implemented against low-difficulty header spam: **header pre-syncing**.

To recapitulate, since the [headers-first synchronization](https://github.com/bitcoin/bitcoin/pull/4468) introduced in Bitcoin Core 0.10.0, blocks are never downloaded before their headers are known and verified to have sufficient work (which means: enough to within one day of the active chain tip, and more than the preconfigured minimum chain work). This means we already don't need to worry about low-difficulty *block* spam anymore. The blocks are just not downloaded unless they're part of a chain that's proven to be good enough.

Yet, a weaker problem remained: a peer could start giving us (multiple) chains of headers that never amount to anything valuable. Since the headers are sent in forward order, there is no way to know at the beginning how good the result will get. The old solution (checkpoints) is unsatisfactory: it relies on updated software with hardcoded overrides on what chain is acceptable. As of Bitcoin Core 26.0, [the checkpoints remain](https://github.com/bitcoin/bitcoin/blob/v26.0/src/kernel/chainparams.cpp#L157L173), but haven't been updated since 2014. By now, mining has become so much cheaper per hash, that an attacker could realistically start an attack *after* the last checkpoint.

Since Bitcoin Core 24.0, a new solution has been implemented: [headers pre-syncing](https://github.com/bitcoin/bitcoin/pull/25717).

The idea is that the header synchronization (which precedes the block synchronization) is split up in two phases:

* In a first phase, *header presyncing*, headers from a peer are downloaded and verified, but **not stored**, because we don't yet know whether they'll end up being good enough. Instead, only a very compact (salted) hash of these headers is kept (in per-peer memory, discarded upon disconnect).
* In a second phase, *header redownloading*, the same headers are downloaded again from the same peer, and compared to the stored hash(\*). If there is a match, they're fed to full header validation, which stores them, and will trigger block download.

This approach comes at a cost - the header synchronization is effectively performed twice, doubling its bandwidth cost (which is still small compared to full block download), but removes the last reliance on checkpoints in the codebase. They'll likely be removed in some future version.

(\*) A simple hash wouldn't be sufficient, as we need the ability to verify headers along the way, not just the end. The actual structure consists of a single 1-bit salted hash every ~600 blocks. To compensate for the (extremely) small hash, upon redownloading, there is a buffer of ~14000 headers that are downloaded before validation. Only if all the ~23 1-bit hashes in those 14000 headers match, then the beginning of the buffer is fed to validation. This means every header has some 23 bits checked against it before validation, which would cost an attacker millions of tries to succeed against.


---

### Answer by username_1 on 2018-06-07. Score: 6

> since checkpoints are removed, we would be vulnerable to this attack

Where did you see that checkpoints are removed? They're still in `src/chainparams.cpp` as of current master (commit `f8bcef38f`).

> How does the headers-first synchronization prevent the fill-disk attack?

Headers-first prevents a related attack where the client would accept "orphan" blocks---block with no known parent---and store them until their parent was received. This prevented wasted bandwidth and sped up validation in the normal case but made disk-fill attacks easier in the pathological case.

[![Orphan versus Stale](https://developer.bitcoin.org/_images/en-orphan-stale-definition.svg)](https://developer.bitcoin.org/_images/en-orphan-stale-definition.svg)

Headers first allows Bitcoin Core to discover the most proof of work block chain (headers chain) that its peers know about before downloading any blocks, which allows it to ensure any blocks it receives are on that chain. This, in turn, means that it never needs to download or store orphan blocks.

> A syncing node downloads all headers from a single peer. If that peer only sends headers from its malicious low-difficulty branch, won't the syncing node try to download all those blocks, and get it's disk filled?

That's correct, and that's why checkpoints are still used in the code as far as I know. My understanding is that, for the ultimate removal of checkpoints, three things were desired:

* Minimum chainwork: a feature coded into a node that tells it the legitimate chain must have at least X amount of chainwork, with X being set to the value for a recent block near the time of a software release. This replaces the original use of checkpoints in preventing network-level attackers from feeding clients long, low-PoW chains containing valid blocks but which aren't the consensus best block chain. This was deployed in Bicoin Core 0.13.2.
* Assumed Valid Blocks: a feature designed to replace the secondary use of checkpoints for (optionally) speeding up Initial Block Download (IBD) by skipping validation of signatures in old blocks. This was deployed in Bitcoin Core 0.14
* A minimum difficult soft fork: a change to directly address the block-fill (or header-fill) attack you described by raising the minimum difficulty at various epochs in the block chain to correspond roughly with the actual observed increases in difficulty. This would make it more expensive for an attacker to feed fake blocks to a node. To the best of my knowledge this has not yet reached the BIP stage and I'm not sure it's currently being actively championed.

For reference, this topic was discussed in the 2017-03-02 Bitcoin Core developer meeting: <https://bitcoincore.org/en/meetings/2017/03/02/#discussion>

> **Comments:**
> **username_0**: Yes, seems I was wrong in assuming that the old checkpoints had been removed. So without the minimum difficulty soft fork, we're still vulnerable to the disk-fill attack from the last checkpoint, which is at height 295000. The difficulty has increased about 1000x since then, so the attack may be feasible.  I also see a line  '         // The best chain should have at least this much work.         consensus.nMinimumChainWork = uint256S("0x000000000000000000000000000000000000000000f91c579d57cad4bc5278cc"); ' Which seems to require any chain to have a lot of PoW to be considered ok.  
> **username_0**: I see there's a `nMinimumChainWork` parameter, that's used in https://github.com/bitcoin/bitcoin/blob/f8bcef38fb9be48f0f5908a6c4c0cbe8c5a729d6/src/net_processing.cpp#L1511. That should cover our asses?  
> **username_1**: AFAIK, that doesn't prevent header-fill attacks.  An exclusive peer can still feed you a header chain with 5 trillion diff-1 headers for about the same cost of producing a block at the tip.  80 bytes per header times 5 trillion is 400 terabytes, though probably something else breaks before then (like block header time rolls over).  
> **username_0**: Right. But let's see how many headers we need before consensus rules break. We need montonically increasing MTP. which means that the block timestamps need to increase at least one second per block, right? So we can't have move block headers than there are seconds since genesis block (+/- a few hours). That's about 315,360,000 blocks. 315 million block headers is "just" 25GB. That's fine, since the node should have 200 GB to spare at header sync. This of course relies on 1) Monotone MTP is verified on header sync and 2) Blocks from the future are not allowed during header sync.  
> **username_2**: @username_0: an attacker can produce multiple low-difficulty branches if there was no protection against flooding by low-difficulty headers. Also, blocks only need a timestamp that is strictly larger than the median of the past 11 ones - that translates to incrementing the timestamp every 6 blocks AFAIK.  
> **username_0**: Summarizing the comments gathered: You can't quickly produce a long branch of low-difficulty headers from the last checkpoint with minimal increasing timestamp, because that would force your difficulty to go up. You're better off producing many 1-block branches from last checkpoint. The current difficulty is 1000x that of the last checkpoint, so if the whole network's hash rate cooperated, they couldn't currently produce more than ~6000*80=480kB of headers per hour. I think I can relax for now. Thanks for clarifications @username_2, I should've consulted my own book before posting about MTP :-)  
> **username_2**: This answer is outdated since the introduction of headers pre-syncing in Bitcoin Core 24.0 (PR 25717).  

---

