跳至主要內容
進階

Headers Message

了解 P2P 協議中的 headers 消息,用於傳輸區塊頭數據實現高效同步。

8 分鐘

Headers 消息是比特幣 P2P 協議中用於傳輸區塊頭的消息類型。 它是 getheaders 請求的回應,也用於在啟用 sendheaders 後主動推送新區塊頭。

Headers 消息概述

Headers 消息的用途:

1. 回應 getheaders 請求
┌─────────┐   getheaders    ┌─────────┐
│ Node A  │ ─────────────→  │ Node B  │
│         │    headers      │         │
│         │ ←─────────────  │         │
└─────────┘                 └─────────┘

2. 主動推送新區塊(sendheaders 模式)
┌─────────┐    headers      ┌─────────┐
│ Miner   │ ─────────────→  │ Node    │
│         │                 │         │
└─────────┘                 └─────────┘

特點:
- 只包含區塊頭(80 bytes/個)
- 不包含交易數據
- 可以快速驗證 PoW
- 確定最佳鏈後再下載區塊體

消息格式

Headers 消息結構:

┌─────────────────────────────────────────┐
│ count (varint)                          │
│ headers:                                │
│   ┌─────────────────────────────────┐   │
│   │ version (4 bytes)               │   │
│   │ prev_block (32 bytes)           │   │
│   │ merkle_root (32 bytes)          │   │
│   │ timestamp (4 bytes)             │   │
│   │ bits (4 bytes)                  │   │
│   │ nonce (4 bytes)                 │   │
│   │ txn_count (varint, always 0)    │   │
│   └─────────────────────────────────┘   │
│   × count                               │
└─────────────────────────────────────────┘

每個 header 項:
- 區塊頭: 80 bytes
- txn_count: 1 byte(總是 0)
- 總計: 81 bytes/項

注意:
- txn_count 總是 0
- 這是歷史設計決定
- 原本可能計劃包含交易

限制:
- 最多 2000 個 headers
- 大約 162 KB 最大大小

區塊頭結構

每個區塊頭的詳細結構:

┌────────────────────────────────────────────┐
│ version (4 bytes, int32_t)                 │
│   - 區塊版本號                              │
│   - 包含軟分叉信號位                        │
├────────────────────────────────────────────┤
│ prev_block (32 bytes, uint256)             │
│   - 前一區塊的雜湊                          │
│   - 建立區塊鏈連接                          │
├────────────────────────────────────────────┤
│ merkle_root (32 bytes, uint256)            │
│   - 交易 Merkle 樹根                        │
│   - 承諾區塊內所有交易                      │
├────────────────────────────────────────────┤
│ timestamp (4 bytes, uint32_t)              │
│   - 區塊時間戳                              │
│   - Unix epoch 秒                           │
├────────────────────────────────────────────┤
│ bits (4 bytes, uint32_t)                   │
│   - 難度目標(壓縮格式)                    │
│   - 決定 PoW 要求                           │
├────────────────────────────────────────────┤
│ nonce (4 bytes, uint32_t)                  │
│   - 隨機數                                  │
│   - 礦工調整以滿足 PoW                      │
└────────────────────────────────────────────┘

總計: 80 bytes

驗證流程

收到 headers 後的驗證:

def process_headers(headers):
    for header in headers:
        # 1. 驗證 PoW
        block_hash = hash256(header)
        target = bits_to_target(header.bits)
        if block_hash > target:
            reject("Invalid PoW")

        # 2. 檢查前一區塊
        if not have_header(header.prev_block):
            # 孤立 header,需要請求更多
            send_getheaders()
            return

        # 3. 驗證時間戳
        prev = get_header(header.prev_block)
        if header.timestamp <= median_time_past(prev):
            reject("Timestamp too early")

        # 4. 驗證難度
        expected_bits = calculate_next_work(prev)
        if header.bits != expected_bits:
            reject("Invalid difficulty")

        # 5. 接受 header
        store_header(header)

    # 決定是否請求區塊體
    update_best_chain()

同步過程中的 Headers

IBD 期間的 headers 同步:

流程:
1. 發送 getheaders(locator)
2. 收到 headers (最多 2000 個)
3. 驗證所有 headers
4. 如果收到 2000 個,還有更多
5. 發送新的 getheaders
6. 重複直到收到 < 2000 個

代碼示例:
class HeaderSync:
    def sync(self):
        while True:
            locator = self.build_locator()
            self.send_getheaders(locator)

            headers = self.wait_for_headers(timeout=60)

            if len(headers) == 0:
                print("Headers synced!")
                break

            for header in headers:
                self.process_header(header)

            if len(headers) < 2000:
                print("Headers synced!")
                break

同步 80 萬個區塊:
- 約 400 次 getheaders 請求
- 約 64 MB headers 數據
- 幾分鐘完成(比下載區塊體快得多)

新區塊通告

使用 headers 通告新區塊:

條件:
- 對方發送過 sendheaders
- 新區塊數量 <= 8 個

單個新區塊:
headers = [new_block.header]
send_headers(headers)

多個新區塊(追趕):
# 對方落後了幾個區塊
missing = find_missing_since(peer.best_known)
if len(missing) <= 8:
    send_headers(missing)
else:
    send_inv(missing)  # 太多了

接收方處理:
def on_headers_announcement(headers):
    for h in headers:
        validate_and_store(h)

    # 請求區塊體
    for h in headers:
        if should_download(h):
            request_block(h.hash)

錯誤處理

Headers 消息的錯誤情況:

1. 無效的 PoW
   - 區塊雜湊不滿足難度
   - 嚴重錯誤,斷開連接

2. 斷開的鏈
   - prev_block 未知
   - 發送 getheaders 請求缺失部分

3. 無效的時間戳
   - 過早(< MTP)
   - 過晚(> 當前時間 + 2小時)

4. 錯誤的難度
   - 不符合難度調整規則
   - 可能是錯誤或攻擊

5. 過多的 headers
   - 超過 2000 個
   - 協議違規

錯誤回應:
if header_invalid:
    misbehaving(peer, score=100)  # 嚴重
    disconnect(peer)

if chain_disconnected:
    send_getheaders(locator)  # 嘗試修復

調試與監控

# 監控 headers 同步

# 查看 headers 同步進度
bitcoin-cli getblockchaininfo | jq '{
  blocks: .blocks,
  headers: .headers,
  bestblockhash: .bestblockhash
}'

# 如果 headers > blocks,正在下載區塊體

# 查看節點的 headers 狀態
bitcoin-cli getpeerinfo | jq '.[] | {
  id: .id,
  synced_headers: .synced_headers
}'

# 日誌設置
# bitcoin.conf
debug=net

# 日誌輸出:
# "Received headers from peer=5: 2000 headers"
# "Processed 2000 headers in 0.5s"
# "Headers sync progress: 750000/800000 (93.75%)"

# 計算 headers 消息大小
# count + (81 × n) bytes
# 2000 headers ≈ 162 KB

實現細節

Bitcoin Core 中的實現:

// 處理收到的 headers
void PeerManager::ProcessHeaders(CNode& node,
    const std::vector<CBlockHeader>& headers)
{
    if (headers.empty()) {
        return;
    }

    const CBlockIndex* pindexLast = nullptr;

    for (const CBlockHeader& header : headers) {
        // 驗證 header
        CValidationState state;
        if (!chainman.ProcessNewBlockHeaders(
                {header}, state, &pindexLast)) {
            if (state.IsInvalid()) {
                Misbehaving(node.GetId(),
                    state.GetDoS(),
                    state.GetRejectReason());
                return;
            }
        }
    }

    // 更新節點狀態
    UpdateBlockAvailability(node.GetId(), pindexLast);

    // 可能請求區塊
    if (pindexLast && pindexLast->nChainWork > chainman.ActiveTip()->nChainWork) {
        // 新鏈工作量更大,請求區塊
        RequestBlocksFromPeer(node);
    }
}

// 驗證單個 header
bool CheckBlockHeader(const CBlockHeader& header, CValidationState& state) {
    // 檢查 PoW
    if (!CheckProofOfWork(header.GetHash(), header.nBits, params)) {
        return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER,
            "high-hash", "proof of work failed");
    }
    return true;
}

相關概念

  • Getheaders:區塊頭請求
  • Sendheaders:區塊頭推送模式
  • Headers-First Sync:頭部優先同步
  • Block Header:區塊頭結構
  • IBD:初始區塊下載
已複製連結
已複製到剪貼簿