高級
Compact Blocks
BIP-152 緊湊區塊中繼:減少區塊傳播延遲和頻寬消耗
18 分鐘
概述
Compact Blocks(BIP-152)是一種優化區塊傳播的協議。 由於節點的 mempool 中通常已經有區塊中的大部分交易, 因此只需傳送交易的短 ID,而非完整交易數據,大幅減少頻寬和延遲。
效果: 傳統區塊傳播需要傳送約 1MB 數據,Compact Blocks 通常只需要 ~15KB, 降低了 98% 以上的頻寬消耗。
解決的問題
傳統區塊傳播
傳統流程:
1. 節點 A 挖到新區塊
2. A 發送 inv 訊息給節點 B
3. B 請求區塊 (getdata)
4. A 發送完整區塊 (~1MB)
5. B 驗證區塊
問題:
- 區塊中 99% 的交易 B 已經有了(在 mempool 中)
- 重複傳送這些交易浪費頻寬
- 傳播延遲影響挖礦公平性 Compact Blocks 解決方案
優化流程:
1. 節點 A 挖到新區塊
2. A 直接發送 cmpctblock 給 B
3. cmpctblock 只包含:
- 區塊頭
- 交易短 ID 列表
- 預填充的 coinbase 交易
4. B 用短 ID 從 mempool 重建區塊
5. 如果缺少某些交易,請求完整數據 兩種模式
Low Bandwidth Mode(低頻寬模式)
流程:
1. A → B: inv (區塊公告)
2. B → A: getdata(CMPCT_BLOCK)
3. A → B: cmpctblock
4. B 重建區塊
特點:
- 節點明確請求後才發送
- 適合頻寬受限的節點
- 預設模式 High Bandwidth Mode(高頻寬模式)
流程:
1. A → B: cmpctblock (直接發送,無需請求)
2. B 重建區塊
特點:
- 區塊一挖到就立即發送
- 最低延遲
- 可能收到重複的區塊
- 最多向 3 個節點請求高頻寬模式 短 ID 計算
每個交易被壓縮為 6 字節的短 ID:
計算步驟:
1. 建立 SipHash 密鑰
key = SHA256(block_header || nonce)[:16]
2. 計算交易短 ID
short_id = SipHash-2-4(key, txid)[:6]
為什麼用 SipHash:
- 快速計算
- 足夠的抗碰撞性(6 bytes = 48 bits)
- 使用區塊特定的密鑰防止預計算攻擊 訊息類型
sendcmpct
宣告支援 Compact Blocks:
sendcmpct {
announce: bool, // 是否請求高頻寬模式
version: uint64 // 版本號 (1 或 2)
}
版本:
- 1: 傳統交易 ID
- 2: 使用 SegWit wtxid cmpctblock
緊湊區塊:
cmpctblock {
header: BlockHeader, // 80 bytes
nonce: uint64, // SipHash 密鑰的一部分
short_ids: ShortId[], // 6 bytes × 交易數
prefilled_txs: PrefilledTx[] // 預填充交易
}
PrefilledTx {
index: varint, // 交易在區塊中的位置
tx: Transaction // 完整交易
}
通常只預填充 coinbase 交易 getblocktxn
請求缺失的交易:
getblocktxn {
block_hash: hash,
indexes: varint[] // 缺失交易的索引
} blocktxn
回應缺失的交易:
blocktxn {
block_hash: hash,
transactions: Transaction[]
} 區塊重建
重建流程:
1. 收到 cmpctblock
2. 驗證區塊頭
3. 計算 SipHash 密鑰
4. 對於每個 short_id:
a. 在 mempool 中搜索匹配的交易
b. 可能有多個候選(碰撞)
5. 處理預填充交易
- 將 coinbase 放在正確位置
6. 嘗試重建區塊
a. 如果所有交易都找到且無碰撞 → 成功
b. 如果有缺失 → 發送 getblocktxn
c. 如果有碰撞 → 請求完整區塊
7. 驗證 Merkle root 匹配
8. 完整驗證區塊 碰撞處理
當多個交易有相同的短 ID 時:
碰撞概率:
- 6 bytes = 48 bits
- 區塊中約 2000 筆交易
- mempool 約 50000 筆交易
- 碰撞概率 ≈ 0.1%
處理策略:
1. 如果只有一個 mempool 交易匹配 → 使用它
2. 如果多個交易匹配 → 嘗試所有組合
3. 如果 Merkle root 不匹配 → 回退到完整區塊 TypeScript 實作
計算短 ID
import { siphash } from 'siphash';
import * as crypto from 'crypto';
interface CompactBlockHeader {
blockHeader: Buffer; // 80 bytes
nonce: bigint;
}
function computeSipHashKey(header: CompactBlockHeader): Buffer {
const data = Buffer.concat([
header.blockHeader,
Buffer.from(header.nonce.toString(16).padStart(16, '0'), 'hex'),
]);
const hash = crypto.createHash('sha256').update(data).digest();
return hash.slice(0, 16); // 128-bit key
}
function computeShortId(key: Buffer, txid: Buffer): Buffer {
// SipHash-2-4
const k0 = key.readBigUInt64LE(0);
const k1 = key.readBigUInt64LE(8);
const hash = siphash24(k0, k1, txid);
// 取前 6 bytes
const shortId = Buffer.alloc(6);
shortId.writeBigUInt64LE(hash, 0);
return shortId.slice(0, 6);
}
// 簡化的 SipHash-2-4 實作
function siphash24(k0: bigint, k1: bigint, data: Buffer): bigint {
// 實際實作需要完整的 SipHash 算法
// 這裡省略細節
return 0n;
} 區塊重建
interface CompactBlock {
header: Buffer;
nonce: bigint;
shortIds: Buffer[];
prefilledTxs: Array<{ index: number; tx: Buffer }>;
}
interface Mempool {
getTxByShortId(shortId: Buffer): Buffer | null;
getTxById(txid: string): Buffer | null;
}
interface ReconstructionResult {
success: boolean;
block?: Buffer;
missingIndexes?: number[];
}
function reconstructBlock(
compact: CompactBlock,
mempool: Mempool
): ReconstructionResult {
const sipKey = computeSipHashKey({
blockHeader: compact.header,
nonce: compact.nonce,
});
const txs: (Buffer | null)[] = new Array(
compact.shortIds.length + compact.prefilledTxs.length
).fill(null);
// 填入預填充交易
for (const prefilled of compact.prefilledTxs) {
txs[prefilled.index] = prefilled.tx;
}
// 從 mempool 匹配交易
const missingIndexes: number[] = [];
let shortIdIndex = 0;
for (let i = 0; i < txs.length; i++) {
if (txs[i] !== null) continue; // 已預填充
const shortId = compact.shortIds[shortIdIndex++];
const tx = mempool.getTxByShortId(shortId);
if (tx) {
txs[i] = tx;
} else {
missingIndexes.push(i);
}
}
if (missingIndexes.length > 0) {
return { success: false, missingIndexes };
}
// 構建完整區塊
const block = buildBlock(compact.header, txs as Buffer[]);
// 驗證 Merkle root
if (!verifyMerkleRoot(compact.header, txs as Buffer[])) {
return { success: false };
}
return { success: true, block };
}
function buildBlock(header: Buffer, txs: Buffer[]): Buffer {
// 構建完整區塊
const txCount = Buffer.alloc(3);
// ... 序列化邏輯
return Buffer.concat([header, txCount, ...txs]);
}
function verifyMerkleRoot(header: Buffer, txs: Buffer[]): boolean {
const merkleRoot = header.slice(36, 68);
const computedRoot = computeMerkleRoot(txs);
return merkleRoot.equals(computedRoot);
}
function computeMerkleRoot(txs: Buffer[]): Buffer {
// 計算 Merkle root
// ... 實作略
return Buffer.alloc(32);
} Mempool 索引
class CompactBlockMempool {
private txs: Map<string, Buffer> = new Map();
private shortIdIndex: Map<string, string[]> = new Map();
private currentKey?: Buffer;
addTransaction(txid: string, tx: Buffer): void {
this.txs.set(txid, tx);
this.invalidateIndex();
}
removeTransaction(txid: string): void {
this.txs.delete(txid);
this.invalidateIndex();
}
private invalidateIndex(): void {
this.shortIdIndex.clear();
this.currentKey = undefined;
}
buildIndex(sipKey: Buffer): void {
if (this.currentKey?.equals(sipKey)) {
return; // 索引仍然有效
}
this.shortIdIndex.clear();
this.currentKey = sipKey;
for (const [txid, _] of this.txs) {
const shortId = computeShortId(
sipKey,
Buffer.from(txid, 'hex')
).toString('hex');
const existing = this.shortIdIndex.get(shortId) || [];
existing.push(txid);
this.shortIdIndex.set(shortId, existing);
}
}
findByShortId(shortId: Buffer): Buffer[] {
const key = shortId.toString('hex');
const txids = this.shortIdIndex.get(key) || [];
return txids
.map((txid) => this.txs.get(txid))
.filter((tx): tx is Buffer => tx !== undefined);
}
} 效能優化
預填充策略
預填充哪些交易:
1. Coinbase (必須)
- 接收方 mempool 中不會有
2. 低傳播交易
- 剛剛加入的交易
- mempool 中可能還沒有
3. 非標準交易
- 可能被其他節點拒絕
Bitcoin Core 預設:
- 只預填充 coinbase
- 保持最小化頻寬 高頻寬節點選擇
選擇標準:
- 連接時間最長的節點
- 區塊中繼最快的節點
- 最多 3 個節點
目的:
- 確保快速收到新區塊
- 避免依賴單一節點 統計數據
# 查看 Compact Block 統計
bitcoin-cli getpeerinfo | jq '.[].bytesrecv_per_msg'
# 區塊重建成功率
# 通常 > 99% 不需要額外請求
# 頻寬節省
# 完整區塊: ~1,000,000 bytes
# Compact Block: ~15,000 bytes
# 節省: ~98.5% 配置選項
# bitcoin.conf
# 啟用 Compact Blocks(預設啟用)
blockreconstructionextratxn=100
# 保留的額外交易數量用於重建
# 即使交易被移出 mempool 也保留一段時間 相關協議
Erlay (BIP-330)
交易中繼優化,與 Compact Blocks 互補:
- Compact Blocks 優化區塊傳播
- Erlay 優化交易傳播
- 兩者結合可大幅降低節點頻寬需求
相關資源
已複製連結
討論