跳至主要內容
進階

Sendheaders Message

了解 P2P 協議中的 sendheaders 消息,啟用區塊頭直接推送模式。

6 分鐘

Sendheaders(BIP-130)是比特幣 P2P 協議中用於請求區塊頭直接推送的消息。 啟用後,對等節點會使用 headers 消息而非 inv 來通告新區塊,減少延遲。

Sendheaders 概述

Sendheaders 的目的:

傳統模式(使用 inv):
┌─────────┐    inv(block)    ┌─────────┐
│ Node A  │ ──────────────→  │ Node B  │
│         │  getheaders      │         │
│         │ ←──────────────  │         │
│         │    headers       │         │
│         │ ──────────────→  │         │
└─────────┘                  └─────────┘
需要 3 次消息往返

Sendheaders 模式:
┌─────────┐    headers       ┌─────────┐
│ Node A  │ ──────────────→  │ Node B  │
└─────────┘                  └─────────┘
只需 1 次消息

優勢:
1. 減少往返延遲
2. 更快的區塊傳播
3. 簡化通訊流程

消息格式

Sendheaders 消息格式:

┌─────────────────────────────────────────┐
│ (empty payload - 0 bytes)               │
└─────────────────────────────────────────┘

完整 P2P 消息:
┌──────────────────────────────────────────────┐
│ magic (4 bytes)                              │
│ command: "sendheaders" (12 bytes, padded)    │
│ length: 0 (4 bytes)                          │
│ checksum: 0x5df6e0e2 (4 bytes)               │
│ payload: (empty)                             │
└──────────────────────────────────────────────┘

發送時機:
- 版本握手完成後
- 通常在 verack 之後立即發送
- 只需發送一次

// 非常簡單的消息,只是一個標誌

協議行為

Sendheaders 後的行為變化:

發送 sendheaders 前:
- 新區塊通過 inv 通告
- 接收方需要請求 headers
- 傳統的請求-回應模式

發送 sendheaders 後:
- 新區塊直接發送 headers
- 無需額外請求
- 接收方驗證後請求區塊體

發送方邏輯:
class Peer:
    prefer_headers = False

    def on_sendheaders(self):
        self.prefer_headers = True

    def announce_block(self, block):
        if self.prefer_headers:
            # 直接發送 header
            self.send_headers([block.header])
        else:
            # 傳統方式
            self.send_inv(block.hash)

// 雙向獨立:A 發送 sendheaders 只影響 A 收到的通告

Headers 通告

使用 headers 通告新區塊:

單個區塊通告:
┌─────────────────────────────────────────┐
│ count: 1                                │
│ headers:                                │
│   block_header (80 bytes)               │
│   txn_count: 0                          │
└─────────────────────────────────────────┘

多個區塊(追趕):
┌─────────────────────────────────────────┐
│ count: 8                                │
│ headers:                                │
│   block_header_1 (80 bytes)             │
│   txn_count: 0                          │
│   block_header_2 (80 bytes)             │
│   txn_count: 0                          │
│   ...                                   │
└─────────────────────────────────────────┘

規則:
- 最多發送 8 個連續 headers
- 如果超過 8 個,回退到 inv 模式
- 確保不會發送過長的消息

何時回退到 inv:
if blocks_to_announce > 8:
    send_inv(blocks)
else:
    send_headers(blocks)

接收方處理

處理 headers 通告:

def process_headers_announcement(headers):
    for header in headers:
        # 驗證 header
        if not validate_header(header):
            misbehaving(peer)
            return

        # 檢查是否已有
        if have_block(header.hash):
            continue

        # 檢查父區塊
        if not have_block(header.prev_hash):
            # 缺少父區塊,需要同步
            send_getheaders(locator)
            return

        # 接受 header
        add_header(header)

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

注意事項:
- 驗證 PoW
- 檢查時間戳
- 確保連接到已知鏈

BIP-130 規範

BIP-130 詳細規範:

引入版本:
- 協議版本 70012+
- Bitcoin Core 0.12.0+

行為定義:
1. 發送 sendheaders 表示偏好 headers 通告
2. 接收方應記住此偏好
3. 新區塊使用 headers 而非 inv 通告
4. 最多 8 個連續 headers
5. 超過則使用 inv

與 sendcmpct 的關係:
- sendheaders: 使用 headers 通告
- sendcmpct (high bandwidth): 直接發送 cmpctblock
- 兩者可以共存

優先級:
if sendcmpct_high_bandwidth:
    send_cmpctblock()
elif sendheaders:
    send_headers()
else:
    send_inv()

調試與監控

# 監控 sendheaders 狀態

# Bitcoin Core 不直接暴露 sendheaders 狀態
# 但可以通過日誌觀察

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

# 日誌輸出:
# "Sending sendheaders to peer=5"
# "peer=3 prefers headers announcements"
# "Announcing block via headers to peer=5"

# 觀察區塊通告方式
# 如果看到 "sending headers" 而非 "sending inv"
# 說明 sendheaders 已啟用

# 檢查節點是否支持
bitcoin-cli getpeerinfo | jq '.[] | {
  id: .id,
  version: .version,
  subver: .subver
}'

# version >= 70012 支持 sendheaders

實現細節

Bitcoin Core 中的實現:

// 發送 sendheaders
void PeerManager::SendSendHeaders(CNode& node) {
    if (node.GetCommonVersion() >= SENDHEADERS_VERSION) {
        connman.PushMessage(&node,
            CNetMsgMaker(node.GetCommonVersion())
                .Make(NetMsgType::SENDHEADERS));
    }
}

// 處理收到的 sendheaders
void PeerManager::ProcessSendHeaders(CNode& node) {
    node.fPreferHeaders = true;
}

// 通告新區塊
void PeerManager::AnnounceBlock(const CBlockIndex* pindex) {
    for (auto& node : connman.GetNodes()) {
        if (node.fPreferHeaders) {
            // 收集要發送的 headers
            std::vector<CBlockHeader> vHeaders;
            // ... 收集連續的 headers ...

            if (vHeaders.size() <= MAX_HEADERS_ANNOUNCE) {
                // 使用 headers 通告
                connman.PushMessage(&node,
                    CNetMsgMaker(node.GetCommonVersion())
                        .Make(NetMsgType::HEADERS, vHeaders));
            } else {
                // 太多了,使用 inv
                AnnounceBlockViaInv(node, pindex);
            }
        } else {
            AnnounceBlockViaInv(node, pindex);
        }
    }
}

const int SENDHEADERS_VERSION = 70012;
const size_t MAX_HEADERS_ANNOUNCE = 8;

相關概念

  • Headers Message:區塊頭消息
  • Getheaders:區塊頭請求
  • Compact Blocks:緊湊區塊
  • Inv Message:庫存通告
  • Headers-First Sync:頭部優先同步
已複製連結
已複製到剪貼簿