高級
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
已複製連結