跳至主要內容
高級

Block Download

深入了解 Bitcoin Core 的區塊下載策略,並行下載和 IBD 優化。

12 分鐘

區塊下載概覽

Bitcoin Core 使用 Headers-First 同步策略,先下載所有區塊頭, 然後並行從多個節點下載區塊數據。這大大加快了初始區塊下載(IBD)速度。

同步階段

階段 描述 並行
1. Headers 下載所有區塊頭 單一節點
2. Blocks 下載區塊數據 多節點並行
3. Validation 驗證和處理區塊 順序處理

Headers-First 同步

Headers-First 流程:

1. 請求區塊頭
   Node A ──getheaders──▶ Peer
   Node A ◀──headers────── Peer   # 最多 2000 個區塊頭

2. 驗證區塊頭鏈
   - 檢查 PoW
   - 檢查時間戳
   - 建立區塊樹

3. 選擇最佳鏈
   - 累積工作量最大的鏈

4. 並行下載區塊
   Node A ──getdata(block1)──▶ Peer1
   Node A ──getdata(block2)──▶ Peer2
   Node A ──getdata(block3)──▶ Peer3
   ...

優點:
✓ 可以驗證鏈的有效性再下載數據
✓ 可以並行從多個節點下載
✓ 防止惡意節點浪費頻寬

並行下載

class BlockDownloadManager {
  // 每個節點的飛行中請求
  private mapBlocksInFlight: Map>;

  // 下載窗口大小
  private readonly BLOCK_DOWNLOAD_WINDOW = 1024;

  // 每個節點最大並行請求
  private readonly MAX_BLOCKS_IN_TRANSIT_PER_PEER = 16;

  // 請求區塊
  requestBlocks(nodeId: NodeId, headers: BlockHeader[]): void {
    const inFlight = this.mapBlocksInFlight.get(nodeId) || new Set();

    // 檢查節點容量
    if (inFlight.size >= this.MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
      return;
    }

    // 選擇要請求的區塊
    const toRequest: BlockHash[] = [];

    for (const header of headers) {
      // 跳過已有的區塊
      if (this.hasBlock(header.hash)) continue;

      // 跳過已在飛行中的請求
      if (this.isInFlight(header.hash)) continue;

      // 檢查是否在下載窗口內
      if (!this.withinDownloadWindow(header.height)) continue;

      toRequest.push(header.hash);
      inFlight.add(header.hash);

      if (inFlight.size >= this.MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
        break;
      }
    }

    if (toRequest.length > 0) {
      this.sendGetData(nodeId, toRequest);
    }

    this.mapBlocksInFlight.set(nodeId, inFlight);
  }

  // 下載窗口檢查
  withinDownloadWindow(height: number): boolean {
    const currentHeight = this.chainTip.height;
    return height <= currentHeight + this.BLOCK_DOWNLOAD_WINDOW;
  }
}

並行下載可視化

區塊下載窗口:

已處理    │ 下載中/待下載                     │ 超出窗口
──────────┼───────────────────────────────────┼──────────
[✓][✓][✓] │ [↓][↓][↓][·][·][·][·][·][·][·][·] │ [?][?][?]
   ↑      │  ↑  ↑  ↑                          │
鏈尖端    │ 從不同節點並行下載                 │ 等待窗口移動

節點分配示例:
Peer 1: Block 100, 101, 102, 103 (4 blocks in flight)
Peer 2: Block 104, 105, 106, 107 (4 blocks in flight)
Peer 3: Block 108, 109, 110, 111 (4 blocks in flight)
...

停滯檢測

class StallDetection {
  // 停滯超時時間(秒)
  private readonly BLOCK_STALLING_TIMEOUT = 2;

  // 下載超時(秒)
  private readonly BLOCK_DOWNLOAD_TIMEOUT_BASE = 60;
  private readonly BLOCK_DOWNLOAD_TIMEOUT_PER_PEER = 5;

  // 檢查停滯的節點
  checkStalling(): void {
    const now = Date.now();

    // 找到需要的下一個區塊
    const nextBlock = this.getNextBlockToProcess();

    for (const [nodeId, blocks] of this.mapBlocksInFlight) {
      for (const blockHash of blocks) {
        const request = this.getBlockRequest(nodeId, blockHash);
        const elapsed = (now - request.timestamp) / 1000;

        // 計算超時時間
        const peerCount = this.connectedPeers.length;
        const timeout = this.BLOCK_DOWNLOAD_TIMEOUT_BASE +
                        this.BLOCK_DOWNLOAD_TIMEOUT_PER_PEER * peerCount;

        if (elapsed > timeout) {
          // 超時,斷開節點
          this.disconnectNode(nodeId, 'block download timeout');
          break;
        }

        // 檢查停滯(下一個需要的區塊長時間未到)
        if (blockHash === nextBlock &&
            elapsed > this.BLOCK_STALLING_TIMEOUT) {
          // 標記為停滯,從其他節點請求
          this.markStalling(nodeId);
          this.requestFromOtherPeer(blockHash);
        }
      }
    }
  }
}

緊湊區塊

BIP-152 緊湊區塊用於快速傳播新區塊,減少頻寬使用。

// 緊湊區塊格式
interface CompactBlock {
  header: BlockHeader;      // 80 bytes
  nonce: bigint;           // 8 bytes
  shortids: ShortTxId[];   // 6 bytes each
  prefilledtxn: PrefilledTx[]; // 預填充的交易(如 coinbase)
}

// 接收緊湊區塊
async onCompactBlock(cmpctblock: CompactBlock): Promise {
  // 1. 使用 mempool 交易重建區塊
  const block = new Block();
  block.header = cmpctblock.header;

  // 2. 從 mempool 匹配交易
  const missing: number[] = [];

  for (let i = 0; i < cmpctblock.shortids.length; i++) {
    const shortid = cmpctblock.shortids[i];
    const tx = this.mempool.findByShortId(shortid, cmpctblock.nonce);

    if (tx) {
      block.transactions[i] = tx;
    } else {
      missing.push(i);
    }
  }

  // 3. 請求缺少的交易
  if (missing.length > 0) {
    this.sendGetBlockTxn(cmpctblock.header.hash, missing);
  } else {
    // 區塊完整,可以處理
    this.processBlock(block);
  }
}
緊湊區塊 vs 完整區塊:

完整區塊:~1-2 MB
┌────────────────────────────────────┐
│ Header (80 bytes)                  │
│ TX Count                           │
│ TX 1 (完整交易數據)                 │
│ TX 2 (完整交易數據)                 │
│ TX 3 (完整交易數據)                 │
│ ...(約 2000-3000 筆交易)          │
└────────────────────────────────────┘

緊湊區塊:~20-30 KB
┌────────────────────────────────────┐
│ Header (80 bytes)                  │
│ Nonce (8 bytes)                    │
│ ShortID 1 (6 bytes)                │
│ ShortID 2 (6 bytes)                │
│ ShortID 3 (6 bytes)                │
│ ...                                │
│ Prefilled: Coinbase TX             │
└────────────────────────────────────┘

節省頻寬:~98%(大多數交易已在 mempool)

配置選項

# bitcoin.conf

# 最大連接數(影響並行下載)
maxconnections=125

# 最大上傳流量(MB/天)
maxuploadtarget=5000

# 禁用 compact blocks(調試用)
# blocksonly=1

# assumevalid(跳過舊區塊的腳本驗證)
# 大幅加速 IBD
assumevalid=000000000000000000...

# 設置 dbcache(MB)
# 更大的快取 = 更快的 IBD
dbcache=4096

# 並行腳本驗證線程
par=4

監控下載進度

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

# 查看節點連接和下載狀態
bitcoin-cli getpeerinfo | jq '.[] | {
  addr: .addr,
  synced_headers: .synced_headers,
  synced_blocks: .synced_blocks,
  inflight: .inflight | length
}'

# 查看網路資訊
bitcoin-cli getnettotals

# 查看 debug.log 中的下載進度
tail -f ~/.bitcoin/debug.log | grep "UpdateTip"

# 輸出示例
# UpdateTip: new best=000000... height=800000 version=0x20000000
#   log2_work=94.123456 tx=876543210 date='2023-07-25T12:00:00Z'
#   progress=0.999999 cache=450.0MiB(3456789txo)

IBD 優化技巧

建議配置

  • • dbcache=4096 或更高
  • • 使用 SSD 存儲
  • • 確保穩定網路連接
  • • 保持預設 assumevalid

避免事項

  • • 不要頻繁重啟節點
  • • 避免低 dbcache 設置
  • • 不要使用 blocksonly(除非必要)
  • • 避免在 HDD 上運行

總結

  • Headers-First:先下載區塊頭,再並行下載區塊
  • 並行下載:從多個節點同時下載不同區塊
  • 緊湊區塊:BIP-152 大幅減少新區塊傳播頻寬
  • 優化:增加 dbcache 和使用 SSD 加速 IBD
已複製連結
已複製到剪貼簿