# 25 - Compressed Block Headers
DIP: 0025
Title: Compressed Block Headers
Author(s): gabriel-bjg, Thephez, UdjinM6
Special-Thanks: Will Clark
Comments-Summary: No comments yet.
Status: Proposed
Type: Standard
Created: 2022-06-06
License: MIT License
## Motivation
Block headers as exchanged by nodes over the p2p network are currently 81 bytes each.
For low bandwidth nodes who are doing a headers-only sync, reducing the size of the headers can provide a significant bandwidth saving. Also, nodes can support more header-only peers for IBD and protection against eclipse attacks if header bandwidth is reduced.
### Background
Currently headers are sent over the p2p network as a vector of `block_headers`, which are composed of the following sized fields:
|Field | Size |
| - | - |
|Version | 4 bytes
|Previous block hash | 32 bytes
|Merkle root hash | 32 bytes
|Time | 4 bytes
|nBits | 4 bytes
|nonce | 4 bytes
|txn_count | 1 byte
|*Total* | 81 bytes
Some fields can be removed completely, others can be compressed under certain conditions.
## Prior work
This work is a derivation of the following:
* [bitcoin-dev: "Compressed" headers stream - August 2017](https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2017-August/014876.html)
* [Further discussion - December 2017](https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2017-December/015385.html)
* [bitcoin-dev: Optimized Header Sync](https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-March/015851.html)
* [Compressed block headers (Will Clark)](https://github.com/willcl-ark/compressed-block-headers/blob/v1.0/compressed-block-headers.adoc)
## Proposed specification
### block_header2 data type
The following table illustrates the proposed `block_header2` data type specification.
|Field | Size | Compressed |
| - | - | - |
|Bitfield | 1 byte | 1 byte
|Version | 4 bytes | 0 \| 4 bytes
|Previous block hash | 32 bytes | 0 \| 32 bytes
|Merkle root hash | 32 bytes | 32 bytes
|Time | 4 bytes | 2 \| 4 bytes
|nBits | 4 bytes | 0 \| 4 bytes
|nonce | 4 bytes | 4 bytes
|*Total* | 81 bytes | range: 39 - 81 bytes
This compression results in a maximum reduction from an 81 byte header to best-case 39 byte header. In bitcoin a continuous header sync from genesis (requiring a single full 81 byte header followed by only compressed `block_header2`) to height 629,474 was tested using this method and it resulted in a bandwidth reduction from 50.98MB down to 25.86MB, a saving of 49%.
#### Bitfield
To make parsing of header messages easier and further increase header compression, a single byte bitfield was suggested by [gmaxwell](https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2017-December/015397.html). We propose the following amended bitfield meanings (bits re-ordered to match `headers2` field order):
|Bit | Meaning + field size to read |
| - | - |
|0
1
2 | Version: same as the last *distinct* value 1st ... 7th (0 byte field) or a new 32-bit distinct value (4 byte field).
|3 | Previous block hash: Omitted (0 byte field) when bit is `0`; Included (32 byte field) when bit is `1`
|4 | Timestamp: Small offset (2 byte field) when bit is `0`; Full timestamp (4 byte field) when bit is `1`
|5 | nBits: Same as last header (0 byte field) when bit is `0`; New value (4 byte field) when bit is `1`
|6+ | Undefined
This bitfield adds 1 byte for every block in the chain.
#### Version
In most cases the Version field will be identical to one referenced in one of the previous 7 unique versions, as indicated by bits 0, 1, and 2 of the Bitfield.
In bitcoin testing to block 629,474, there were 616,137 blocks whose version was in the previous 7 distinct versions and only 13,338 blocks that were not.
| Genesis to block | Current (B) | Compressed (B) | Saving (%) |
| - | - | - | - |
| 629,474 | 2,517,896 | 53,352 |98 |
#### Previous block hash
The previous block hash will always be the X11 hash of `previous_header` so it is redundant given that you have the previous header in the chain.
| Genesis to block | Current (B) | Compressed (B) | Saving (%) |
| - | - | - | - |
| 629,474 | 20,143,168 | 0 | 100
#### Time
The timestamp (in seconds) is consensus bound, based both on the time in the previous header: `MAX_FUTURE_BLOCK_TIME = 2 * 60 * 60 = 7200`, and being greater than the `MedianTimePast` of the previous 11 blocks. Therefore this can be safely represented as an offset from the previous headers' timestamp using a 2 byte `signed short int`.
| Genesis to block | Current (B) | Compressed (B) | Saving (%) |
| - | - | - | - |
| 629,474 | 2,517,896 | 1,258,952 | 50
#### nBits
In Dash use of the [Dark Gravity Wave (DGW)](https://docs.dash.org/en/stable/introduction/features.html#dark-gravity-wave) difficulty adjustment algorithm results in nBits generally changing in every block. Consequently, this field will usually contain the full value.
There is a period of time at the beginning of the chain where the bitcoin difficulty adjustment algorithm was used. During this time compression of the nBits field will occur.
#### txn_count
txn_count is included to make parsing of these messages compatible with parsing of `block` messages as explained on the [Bitcoin StackExchange](https://bitcoin.stackexchange.com/questions/2104/why-is-the-block-header-txn-count-field-always-zero). Therefore this field and its associated byte can be removed for transmission of compact headers.
| Genesis to block | Current (B) | Compressed (B) | Saving (%) |
| - | - | - | - |
| 629,474 | 629,474 | 0 | 100
### Service Bit
A new service bit is required so that the nodes can advertise their ability to supply compact headers. Dash would use bit 11 and designate it `NODE_HEADERS_COMPRESSED`.
### P2P Messages
Three new messages would be used by nodes that enable compact block header support, two query messages: `getheaders2` and `sendheaders2` and one response: `headers2`.
#### `getheaders2` -- Requesting compact headers
The new p2p message required to request compact block headers would require the same fields as the current `getheaders` message:
|Field Size | Description | Data type | Comments
| - | - | - | -
|4 |version |uint32_t |The protocol version
|1+ |hash count |var_int |Number of block locator hash entries
|32+ |block locator hashes |char[32] |Block locator object; newest back to genesis block (dense to start, but then sparse)
|32 |hash_stop |char[32] |Hash of the last desired block header; set to zero to get as many blocks as possible (2000)
#### `sendheaders2` -- Request compact header announcements
Since [BIP-130](https://github.com/bitcoin/bips/blob/master/bip-0130.mediawiki), nodes have been able to request to receive new headers directly in `headers` messages, rather than via an `inv` of the new block hash and subsequent `getheader` request and `headers` response (followed by a final `getdata` to get the tip block itself, if desired). This is requested by transmitting an empty `sendheaders` message after the version handshake is complete.]
Upon receipt of this message, the node is permitted, but not required, to preemptively announce new headers with the `headers2` message (instead of `inv`). Preemptive header announcement has been supported by the protocol version ≥ 70206 | Dash Core version ≥ 0.12.1.
For the motivational use-case it makes sense to also update this mechanism to support sending header updates using compact headers using a new message.
#### `headers2` -- Receiving compact headers
A `headers2` message is returned in response to `getheaders2` or at new header announcement following a `sendheaders2` request. It contains both `length` and `headers` fields. The `headers` field contains a variable length vector of `block_header2`:
| Field Size | Description | Data type | Comments
| - | - | - | -
|1+ |length |var_int |Length of `headers`
|39-81x? |headers |block_header2[] |Compressed block headers in [block_header2 data type](#block_header2-data-type) format
### Implementation
* The first header in each `block_header2[]` vector MUST contain the full `nBits`, `timestamp`, `version` and `prev_block_hash` fields, along with a correctly populated `bitfield` byte.
* Subsequent headers in a contiguous vector SHOULD follow the compressed [block_header2 data type](#block_header2-data-type) format.
* If a peer sends both a `sendheaders` and a `sendheaders2` message, they SHOULD be sent block announcements using compressed headers regardless of which message was sent first.
# Copyright
Copyright (c) 2022 Dash Core Group, Inc. [Licensed under the MIT
License](https://opensource.org/licenses/MIT)