進階
Headers-First Sync
Headers-First 同步:比特幣節點的現代初始區塊下載策略
14 分鐘
概述
Headers-First 同步是 Bitcoin Core 0.10.0(2015年)引入的初始區塊下載(IBD)策略。 它先下載所有區塊頭,驗證工作量證明鏈,然後再並行下載區塊數據。 這大幅提升了同步速度和安全性。
核心優勢: 區塊頭只有 80 字節,整條鏈約 60 MB。先下載頭部可以快速確定最長鏈, 避免下載無效或孤立的區塊數據。
舊同步方法
Blocks-First 問題
舊方法: Blocks-First (v0.9.x 及更早)
流程:
1. 連接節點
2. 發送 getblocks 請求
3. 收到 inv 訊息 (區塊清單)
4. 逐個請求並下載區塊
5. 驗證並存儲
6. 重複直到同步完成
問題:
1. 順序下載
┌────┐ ┌────┐ ┌────┐ ┌────┐
│ B1 │──▶│ B2 │──▶│ B3 │──▶│ B4 │
└────┘ └────┘ └────┘ └────┘
必須等待前一個區塊才能驗證下一個
2. 容易受到攻擊
- 攻擊者可以發送舊的無效鏈
- 浪費頻寬下載無用區塊
- 無法提前知道哪條是最長鏈
3. 單一節點瓶頸
- 一次只能從一個節點下載
- 網路利用率低 Headers-First 方法
同步流程
Headers-First 同步流程:
階段 1: 下載區塊頭
┌─────────────────────────────────────────────┐
│ getheaders → headers → getheaders → ... │
│ │
│ 只下載 80 bytes × 區塊數量 │
│ 約 60 MB (850,000 區塊) │
│ 耗時: 幾秒到幾分鐘 │
└─────────────────────────────────────────────┘
↓
階段 2: 驗證區塊頭鏈
┌─────────────────────────────────────────────┐
│ 對每個區塊頭: │
│ - 驗證 PoW │
│ - 驗證 prev_hash 連接 │
│ - 驗證時間戳 │
│ - 計算累積工作量 │
│ │
│ 確定最長鏈 (最多工作量) │
└─────────────────────────────────────────────┘
↓
階段 3: 並行下載區塊
┌─────────────────────────────────────────────┐
│ Peer A Peer B Peer C │
│ ↓ ↓ ↓ │
│ [B1-B100] [B101-B200] [B201-B300] │
│ │
│ 多個節點並行下載不同區塊 │
│ 顯著提升下載速度 │
└─────────────────────────────────────────────┘
↓
階段 4: 驗證區塊
┌─────────────────────────────────────────────┐
│ 按順序驗證每個區塊: │
│ - 驗證所有交易 │
│ - 更新 UTXO 集合 │
│ - 保存到磁碟 │
└─────────────────────────────────────────────┘ 訊息流程
P2P 訊息序列:
1. 請求區塊頭
───────────────────────────────────────
→ getheaders
├─ version: 70015
├─ hash_count: n
├─ block_locator: [hash1, hash2, ...]
└─ hash_stop: 0x00...00
← headers
├─ count: 2000 (最多)
└─ headers: [header1, header2, ...]
(重複直到收到空 headers)
2. 請求區塊數據
───────────────────────────────────────
→ getdata
├─ count: n
└─ inventory: [
{type: MSG_BLOCK, hash: ...},
{type: MSG_BLOCK, hash: ...},
...
]
← block (多個)
└─ 完整區塊數據 實作細節
區塊定位器
// 區塊定位器 (Block Locator)
// 用於找到與其他節點的共同區塊
function buildBlockLocator(
chainTip: number,
getBlockHash: (height: number) => string
): string[] {
const locator: string[] = [];
let height = chainTip;
let step = 1;
while (height >= 0) {
locator.push(getBlockHash(height));
if (height === 0) break;
// 前 10 個每步退 1,之後指數退
height -= step;
if (locator.length >= 10) {
step *= 2;
}
if (height < 0) height = 0;
}
return locator;
}
// 範例: 鏈高度 1000
// 返回: [1000, 999, 998, ..., 991, 989, 985, 977, ...]
// 這樣可以快速找到分叉點
// 接收方處理
function findForkPoint(
locator: string[],
isKnownBlock: (hash: string) => boolean
): string | null {
for (const hash of locator) {
if (isKnownBlock(hash)) {
return hash;
}
}
return null; // 從創世區塊開始
} 區塊頭驗證
interface BlockHeader {
version: number;
prevHash: Uint8Array;
merkleRoot: Uint8Array;
timestamp: number;
bits: number;
nonce: number;
}
interface HeaderValidation {
valid: boolean;
error?: string;
}
function validateHeader(
header: BlockHeader,
prevHeader: BlockHeader | null,
currentTime: number
): HeaderValidation {
// 1. 驗證工作量證明
const headerHash = computeBlockHash(header);
const target = bitsToTarget(header.bits);
if (hashToBigInt(headerHash) > target) {
return { valid: false, error: 'Insufficient proof of work' };
}
// 2. 驗證 prevHash 連接
if (prevHeader) {
const prevHash = computeBlockHash(prevHeader);
if (!bytesEqual(header.prevHash, prevHash)) {
return { valid: false, error: 'Invalid prev_hash' };
}
}
// 3. 驗證時間戳
if (header.timestamp > currentTime + 2 * 60 * 60) {
return { valid: false, error: 'Timestamp too far in future' };
}
// 4. 驗證難度 (如果是調整點)
// ... 難度調整驗證
return { valid: true };
}
// 批量驗證區塊頭鏈
async function validateHeaderChain(
headers: BlockHeader[]
): Promise<{ valid: boolean; totalWork: bigint }> {
let prevHeader: BlockHeader | null = null;
let totalWork = 0n;
const currentTime = Math.floor(Date.now() / 1000);
for (const header of headers) {
const result = validateHeader(header, prevHeader, currentTime);
if (!result.valid) {
return { valid: false, totalWork: 0n };
}
// 累積工作量
totalWork += calculateWork(header.bits);
prevHeader = header;
}
return { valid: true, totalWork };
}
// 計算區塊工作量
function calculateWork(bits: number): bigint {
const target = bitsToTarget(bits);
// work = 2^256 / (target + 1)
return (1n << 256n) / (target + 1n);
} 並行區塊下載
interface DownloadManager {
peers: Peer[];
pendingBlocks: Map<string, number>; // hash → assigned peer
downloadedBlocks: Map<string, Block>;
nextToValidate: number; // 下一個待驗證的高度
}
class BlockDownloader {
private windowSize = 1024; // 最多同時請求的區塊數
private blocksPerPeer = 16; // 每個節點一次請求的區塊數
async downloadBlocks(
headers: BlockHeader[],
peers: Peer[]
): Promise<void> {
const queue = headers.map((h, i) => ({
hash: computeBlockHash(h),
height: i
}));
let nextIndex = 0;
const inFlight = new Map<string, { peer: Peer; timeout: number }>();
const downloaded = new Map<number, Block>();
let nextToProcess = 0;
while (nextToProcess < headers.length) {
// 分配下載任務
for (const peer of peers) {
const peerTasks = [...inFlight.values()]
.filter(t => t.peer === peer).length;
while (peerTasks < this.blocksPerPeer &&
nextIndex < queue.length &&
inFlight.size < this.windowSize) {
const task = queue[nextIndex++];
this.requestBlock(peer, task.hash);
inFlight.set(task.hash, {
peer,
timeout: Date.now() + 30000
});
}
}
// 處理已下載的區塊
while (downloaded.has(nextToProcess)) {
const block = downloaded.get(nextToProcess)!;
await this.processBlock(block);
downloaded.delete(nextToProcess);
nextToProcess++;
}
// 處理超時
for (const [hash, task] of inFlight) {
if (Date.now() > task.timeout) {
inFlight.delete(hash);
// 重新加入隊列
}
}
await sleep(100);
}
}
private async requestBlock(peer: Peer, hash: string): Promise<void> {
await peer.send('getdata', [{
type: MSG_BLOCK,
hash
}]);
}
private async processBlock(block: Block): Promise<void> {
// 驗證並存儲區塊
await this.validateBlock(block);
await this.saveBlock(block);
}
} 檢查點
內建檢查點 (Checkpoints):
Bitcoin Core 內建了特定高度的區塊雜湊:
static const CCheckpointData data = {
{
{ 11111, "0x0000000069e244f73d78e8fd29ba2fd2..."},
{ 33333, "0x000000002dd5588a74784eaa7ab0507a..."},
{295000, "0x00000000000000004d9b4ef50f0f9d68..."},
...
}
};
作用:
1. 加速初始同步
- 跳過檢查點之前的難度驗證
- 假設這些區塊已被充分驗證
2. 防止長程攻擊
- 攻擊者無法創建替代歷史
- 必須包含這些區塊
3. 爭議
- 有人認為這增加了中心化
- 檢查點需要開發者手動更新
AssumeValid:
- 更現代的替代方案
- 假設特定區塊之前的簽名有效
- 不影響區塊連接驗證 IBD 狀態
初始區塊下載 (IBD) 狀態:
判斷是否在 IBD:
1. 鏈尖時間超過 24 小時前
2. 鏈尖工作量低於已知最大值
IBD 期間的特殊行為:
1. 區塊請求
- 只從一個節點請求區塊頭
- 區塊數據可以並行下載
2. 交易處理
- 不接受 mempool 交易
- 不轉發交易
3. 區塊驗證
- 可以跳過簽名驗證 (如果使用 assumevalid)
- 仍然驗證區塊結構
4. 網路行為
- 不響應 getdata 請求
- 不廣播 addr 訊息
IBD 完成後:
- 進入正常模式
- 開始處理 mempool
- 參與區塊中繼 效能比較
同步時間對比:
測試條件: 2024年,850,000 區塊
Blocks-First (估計):
- 順序下載
- ~500 GB 數據
- 預計: 數天到數週
Headers-First:
- 階段 1 (headers): ~5 分鐘
- 階段 2 (blocks): 取決於頻寬
- 階段 3 (verify): 取決於 CPU
- 總計: 數小時到數天
Headers-First + AssumeValid:
- 跳過歷史簽名驗證
- 進一步減少 CPU 時間
- 總計: 數小時
Headers-First + AssumeUTXO:
- 從快照開始
- 幾分鐘內可用
- 背景完成完整同步
頻寬利用:
┌─────────────────────────────────────┐
│ Blocks-First: │
│ ████░░░░░░░░░░░░░░ (單節點) │
│ │
│ Headers-First: │
│ ████████████████████ (多節點並行) │
└─────────────────────────────────────┘ 安全考量
Headers-First 安全優勢:
1. 防止浪費頻寬
- 先驗證 PoW 再下載區塊
- 攻擊者無法讓你下載無效數據
2. 快速識別最長鏈
- 幾分鐘內知道全局最佳鏈
- 不會被引導到舊分叉
3. 並行下載的安全性
- 每個區塊獨立驗證
- 惡意節點無法影響其他下載
潛在風險:
1. Header 洪水攻擊
- 攻擊者發送大量無效 headers
- 緩解: 驗證 PoW 再存儲
2. 日蝕攻擊
- 所有連接都被攻擊者控制
- 緩解: 連接多樣化,使用 DNS seeds
3. 時間扭曲
- 操縱時間戳影響難度
- 緩解: 網路時間調整,中位數檢查 配置選項
Bitcoin Core 相關配置:
# 假設特定區塊之前的簽名有效
-assumevalid=<blockhash>
# 禁用 assumevalid
-assumevalid=0
# 檢查點 (已棄用)
-checkpoints (默認啟用)
# IBD 完成前不接受連接
-listen=0 (IBD 期間建議)
# 同時連接的節點數
-maxconnections=<n>
# 並行區塊下載的窗口大小
# (硬編碼,無法配置)
查看同步進度:
$ bitcoin-cli getblockchaininfo
{
"blocks": 850000,
"headers": 850000,
"verificationprogress": 0.9999,
"initialblockdownload": false,
...
} 相關資源
已複製連結