跳至主要內容
進階

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 膨脹
已複製連結
已複製到剪貼簿