進階
Block Weight
深入了解比特幣的區塊重量系統,SegWit 如何透過 weight units 替代傳統的區塊大小限制。
12 分鐘
從區塊大小到區塊重量
在 SegWit(BIP-141)啟用之前,比特幣區塊有嚴格的 1 MB 大小限制。SegWit 引入了新的「區塊重量」 (Block Weight)概念,使用 Weight Units (WU) 來衡量區塊大小,最大限制為 4,000,000 WU。 這個改變允許區塊包含更多交易,同時保持向後兼容。
關鍵數字
4 MWU
最大區塊重量
4,000,000 Weight Units
~1.8 MB
典型區塊大小
使用 SegWit 交易
~4 MB
理論最大大小
純見證數據
重量計算
基本公式
區塊和交易的重量計算方式如下:
Weight = (基礎數據大小 × 4) + (見證數據大小 × 1)
或等價於:
Weight = (非見證數據 × 4) + (見證數據 × 1)
= (總大小 × 4) - (見證數據 × 3)
其中:
- 基礎數據:version, inputs, outputs, locktime
- 見證數據:簽名和公鑰(SegWit 專用) Virtual Bytes (vB)
Virtual Bytes 是將 Weight Units 轉換回「等效位元組」的方式,用於手續費計算:
vBytes = Weight / 4(向上取整)
例如:
- Legacy 交易:225 bytes = 900 WU = 225 vB
- SegWit 交易:140 base + 70 witness = 630 WU ≈ 158 vB
手續費率通常以 sat/vB 表示
fee = size_in_vbytes × fee_rate 交易類型比較
| 類型 | 基礎大小 | 見證大小 | 總大小 | Weight | vBytes |
|---|---|---|---|---|---|
| P2PKH (1-in-1-out) | 192 B | 0 B | 192 B | 768 WU | 192 vB |
| P2WPKH (1-in-1-out) | 82 B | 107 B | 189 B | 435 WU | 109 vB |
| P2TR (1-in-1-out) | 87 B | 65 B | 152 B | 413 WU | 104 vB |
| P2PKH (2-in-2-out) | 374 B | 0 B | 374 B | 1496 WU | 374 vB |
| P2WPKH (2-in-2-out) | 130 B | 214 B | 344 B | 734 WU | 184 vB |
SegWit 交易節省約 40-50% 的 vBytes,這意味著同樣的區塊空間可以容納更多交易。
見證折扣
為什麼見證數據有折扣?
- 1. UTXO 集合影響: 基礎數據(輸入輸出)直接影響 UTXO 集合大小,這是節點必須永久存儲的。 見證數據在驗證後可以丟棄。
- 2. 驗證成本: UTXO 查詢是區塊驗證的瓶頸。見證驗證(簽名檢查)相對較快。
- 3. 激勵結構: 折扣鼓勵使用更高效的腳本類型,減少對節點的負擔。
成本比較
基礎數據:1 byte = 4 WU = 1 vByte
見證數據:1 byte = 1 WU = 0.25 vByte
在 10 sat/vB 手續費率下:
- 基礎數據:10 sats/byte
- 見證數據:2.5 sats/byte(75% 折扣)
實際節省(P2WPKH vs P2PKH,單輸入單輸出):
- P2PKH: 192 vB × 10 sat/vB = 1,920 sats
- P2WPKH: 109 vB × 10 sat/vB = 1,090 sats
- 節省: 830 sats (43%) 實現細節
重量計算代碼
interface Transaction {
version: number;
inputs: TxInput[];
outputs: TxOutput[];
locktime: number;
witness?: WitnessData[];
}
interface TxInput {
prevTxHash: Buffer; // 32 bytes
prevIndex: number; // 4 bytes
scriptSig: Buffer; // variable
sequence: number; // 4 bytes
}
interface TxOutput {
value: bigint; // 8 bytes
scriptPubKey: Buffer; // variable
}
const WITNESS_SCALE_FACTOR = 4;
function calculateWeight(tx: Transaction): number {
// 基礎大小(不含見證)
const baseSize = calculateBaseSize(tx);
// 見證大小
const witnessSize = calculateWitnessSize(tx);
// 總重量
return baseSize * WITNESS_SCALE_FACTOR + witnessSize;
}
function calculateVBytes(tx: Transaction): number {
const weight = calculateWeight(tx);
return Math.ceil(weight / WITNESS_SCALE_FACTOR);
}
function calculateBaseSize(tx: Transaction): number {
let size = 0;
// Version (4 bytes)
size += 4;
// Input count (varint)
size += varintSize(tx.inputs.length);
// Inputs
for (const input of tx.inputs) {
size += 32; // prevTxHash
size += 4; // prevIndex
size += varintSize(input.scriptSig.length);
size += input.scriptSig.length;
size += 4; // sequence
}
// Output count (varint)
size += varintSize(tx.outputs.length);
// Outputs
for (const output of tx.outputs) {
size += 8; // value
size += varintSize(output.scriptPubKey.length);
size += output.scriptPubKey.length;
}
// Locktime (4 bytes)
size += 4;
return size;
}
function calculateWitnessSize(tx: Transaction): number {
if (!tx.witness || tx.witness.length === 0) {
return 0;
}
let size = 0;
// Marker and flag (2 bytes total for SegWit transactions)
size += 2;
// Witness data for each input
for (const witnessStack of tx.witness) {
size += varintSize(witnessStack.length);
for (const item of witnessStack) {
size += varintSize(item.length);
size += item.length;
}
}
return size;
}
function varintSize(value: number): number {
if (value < 0xfd) return 1;
if (value <= 0xffff) return 3;
if (value <= 0xffffffff) return 5;
return 9;
}
// 使用範例
const tx: Transaction = {
version: 2,
inputs: [/* ... */],
outputs: [/* ... */],
locktime: 0,
witness: [/* ... */],
};
console.log('Weight:', calculateWeight(tx), 'WU');
console.log('vBytes:', calculateVBytes(tx), 'vB');
console.log('Fee at 10 sat/vB:', calculateVBytes(tx) * 10, 'sats'); 區塊結構變化
SegWit 之前
區塊結構(Pre-SegWit):
┌────────────────────────────────┐
│ Block Header (80 bytes) │
├────────────────────────────────┤
│ Transaction Count (varint) │
├────────────────────────────────┤
│ Transaction 1 │
│ - Version │
│ - Inputs (含 scriptSig/簽名) │
│ - Outputs │
│ - Locktime │
├────────────────────────────────┤
│ Transaction 2 │
│ ... │
└────────────────────────────────┘
最大大小: 1,000,000 bytes SegWit 之後
區塊結構(Post-SegWit):
┌────────────────────────────────┐
│ Block Header (80 bytes) │
├────────────────────────────────┤
│ Transaction Count (varint) │
├────────────────────────────────┤
│ Transaction 1 (Base) │
│ - Version │
│ - Marker (0x00) │ ← SegWit 標記
│ - Flag (0x01) │ ← SegWit 標誌
│ - Inputs (空 scriptSig) │
│ - Outputs │
│ - Witness Data │ ← 見證數據(折扣計算)
│ - Locktime │
├────────────────────────────────┤
│ Transaction 2 │
│ ... │
└────────────────────────────────┘
最大重量: 4,000,000 WU
實際最大大小: ~4 MB(純見證時) 實際限制
理論 vs 實際
| 場景 | 區塊大小 | 說明 |
|---|---|---|
| 純 Legacy 交易 | ~1 MB | 4M WU / 4 = 1M bytes |
| 典型 SegWit 混合 | ~1.5-2 MB | 常見的區塊大小 |
| 純 SegWit 交易 | ~2-2.5 MB | 所有交易使用 SegWit |
| 純見證數據 | ~4 MB | 理論最大值,實際不可能 |
為什麼區塊不是 4 MB?
要達到 4 MB 區塊大小,交易必須只包含見證數據。但實際交易需要:
- • 輸入引用(prevout):最少 36 bytes × 4 = 144 WU
- • 輸出腳本:最少 9 bytes × 4 = 36 WU
- • 交易開銷(version, locktime 等):~40 WU
因此,即使是最優化的交易,也需要相當比例的基礎數據。
手續費估算
手續費計算
interface FeeEstimate {
scriptType: 'p2pkh' | 'p2wpkh' | 'p2tr';
inputCount: number;
outputCount: number;
feeRate: number; // sat/vB
}
// 典型輸入大小(vBytes)
const INPUT_VBYTES = {
p2pkh: 148,
p2wpkh: 68,
p2tr: 58,
} as const;
// 典型輸出大小(vBytes)
const OUTPUT_VBYTES = {
p2pkh: 34,
p2wpkh: 31,
p2tr: 43,
} as const;
// 固定開銷
const OVERHEAD_VBYTES = {
p2pkh: 10, // version + locktime + in/out counts
p2wpkh: 11, // + marker + flag
p2tr: 11,
} as const;
function estimateTxVBytes(params: Omit): number {
const { scriptType, inputCount, outputCount } = params;
const overhead = OVERHEAD_VBYTES[scriptType];
const inputsSize = INPUT_VBYTES[scriptType] * inputCount;
const outputsSize = OUTPUT_VBYTES[scriptType] * outputCount;
return overhead + inputsSize + outputsSize;
}
function estimateFee(params: FeeEstimate): number {
const vBytes = estimateTxVBytes(params);
return vBytes * params.feeRate;
}
// 使用範例
const p2pkhFee = estimateFee({
scriptType: 'p2pkh',
inputCount: 2,
outputCount: 2,
feeRate: 10,
});
console.log('P2PKH 2-in-2-out at 10 sat/vB:', p2pkhFee, 'sats');
// 輸出:3740 sats
const p2wpkhFee = estimateFee({
scriptType: 'p2wpkh',
inputCount: 2,
outputCount: 2,
feeRate: 10,
});
console.log('P2WPKH 2-in-2-out at 10 sat/vB:', p2wpkhFee, 'sats');
// 輸出:2090 sats
// 節省
console.log('Savings:', p2pkhFee - p2wpkhFee, 'sats',
'(' + Math.round((1 - p2wpkhFee / p2pkhFee) * 100) + '%)'); 對挖礦的影響
交易選擇
礦工根據 sat/vB(或 sat/WU)來排序交易,而不是 sat/byte:
interface MempoolTransaction {
txid: string;
weight: number;
fee: number;
}
function sortByFeeRate(txs: MempoolTransaction[]): MempoolTransaction[] {
return [...txs].sort((a, b) => {
const rateA = a.fee / (a.weight / 4); // fee per vByte
const rateB = b.fee / (b.weight / 4);
return rateB - rateA; // 降序
});
}
function selectTransactions(
txs: MempoolTransaction[],
maxWeight: number = 4_000_000
): { selected: MempoolTransaction[]; totalWeight: number; totalFee: number } {
const sorted = sortByFeeRate(txs);
const selected: MempoolTransaction[] = [];
let totalWeight = 0;
let totalFee = 0;
for (const tx of sorted) {
if (totalWeight + tx.weight <= maxWeight) {
selected.push(tx);
totalWeight += tx.weight;
totalFee += tx.fee;
}
}
return { selected, totalWeight, totalFee };
} 區塊模板
# 獲取區塊模板
bitcoin-cli getblocktemplate '{"rules": ["segwit"]}'
# 返回的模板包含:
{
"previousblockhash": "...",
"transactions": [...],
"weight": 3999892, # 區塊總重量(接近 4M 限制)
"sigoplimit": 80000, # sigop 限制
"sigopcost": 12345, # 當前 sigop 成本
"curtime": 1234567890,
"bits": "170d21b9",
"height": 800000,
...
} 相關 RPC 命令
# 查看區塊重量
bitcoin-cli getblock 1 | jq '.weight'
# 查看交易重量
bitcoin-cli getrawtransaction true | jq '{size, vsize, weight}'
# 解碼交易並查看重量
bitcoin-cli decoderawtransaction | jq '{size, vsize, weight}'
# 查看 mempool 統計
bitcoin-cli getmempoolinfo
# {
# "size": 12345,
# "bytes": 12345678,
# "usage": 87654321,
# "total_fee": 0.12345678,
# ...
# }
# 估算手續費(以 sat/vB 為單位)
bitcoin-cli estimatesmartfee 6
# {
# "feerate": 0.00012345, # BTC/kvB
# "blocks": 6
# } 最佳實踐
✓ 推薦做法
- • 使用 SegWit 或 Taproot 地址
- • 以 sat/vB 計算手續費
- • 批量處理輸出以減少總重量
- • 使用高效的腳本類型
⚠ 注意事項
- • 不要用 bytes 估算 SegWit 手續費
- • 考慮輸入的重量成本
- • 監控區塊重量使用率
- • 選擇合適的找零輸出類型
總結
- ✓ Weight Units:4M WU 限制取代了 1 MB 區塊大小限制
- ✓ 見證折扣:見證數據以 1/4 成本計算,鼓勵使用 SegWit
- ✓ vBytes:手續費以 sat/vB 計算,vBytes = Weight/4
- ✓ 實際效果:區塊大小增加到約 2 MB,同時減少 UTXO 膨脹
已複製連結