跳至主要內容
進階

Mining

比特幣挖礦機制:區塊模板、難度調整和礦池協議

20 分鐘

概述

比特幣挖礦是透過工作量證明(Proof of Work)來保護網路安全和發行新幣的過程。 礦工競爭解決計算難題,勝出者獲得打包區塊的權利和區塊獎勵。

經濟激勵: 挖礦獎勵 = 區塊補貼 + 交易手續費。 區塊補貼每 210,000 區塊減半,目前為 3.125 BTC(2024 年減半後)。

工作量證明

挖礦目標

找到一個 nonce,使區塊頭的雜湊值小於目標值:

SHA256(SHA256(block_header)) < target

區塊頭 (80 bytes):
├── version (4 bytes)
├── prev_block_hash (32 bytes)
├── merkle_root (32 bytes)
├── timestamp (4 bytes)
├── bits (4 bytes) - 壓縮的目標值
└── nonce (4 bytes) - 礦工調整的值

難度與目標

difficulty = max_target / current_target

max_target = 0x00000000FFFF0000...0000 (難度 1)

範例:
- 目標: 0x00000000000000000004... (前導零越多越難)
- 難度: 約 80 萬億 (2024 年)

雜湊率單位

單位 雜湊/秒 說明
H/s 1 每秒雜湊
KH/s 10³
MH/s 10⁶ 百萬
GH/s 10⁹ 十億
TH/s 10¹² 兆(現代 ASIC)
PH/s 10¹⁵ 拍(礦池)
EH/s 10¹⁸ 艾(全網)

難度調整

每 2016 個區塊(約 2 週)調整一次難度,目標是維持 10 分鐘的出塊時間:

new_difficulty = old_difficulty × (2 週 / 實際時間)

限制:
- 最多增加 4 倍
- 最多減少到 1/4

計算:
expected_time = 2016 × 10 分鐘 = 20160 分鐘
actual_time = last_block_time - first_block_time

adjustment_factor = expected_time / actual_time
new_target = old_target × adjustment_factor

區塊模板

getblocktemplate RPC

# 獲取區塊模板
bitcoin-cli getblocktemplate '{"rules": ["segwit"]}'

# 結果
{
  "version": 536870912,
  "previousblockhash": "00000000000000000002...",
  "transactions": [...],
  "coinbaseaux": {},
  "coinbasevalue": 312500000,  # 區塊獎勵 (satoshis)
  "target": "00000000000000000004...",
  "mintime": 1700000000,
  "mutable": ["time", "transactions", "prevblock"],
  "noncerange": "00000000ffffffff",
  "sigoplimit": 80000,
  "sizelimit": 4000000,
  "weightlimit": 4000000,
  "curtime": 1700000100,
  "bits": "17034219",
  "height": 800000
}

交易選擇

礦工選擇交易的優先級:

1. 祖先費率 (Ancestor Feerate)
   - 計算整個交易包的平均費率
   - 包括所有未確認的父交易

2. 區塊權重限制
   - 最大 4,000,000 weight units
   - 約 1 MB 實際數據 + witness 折扣

3. 排序算法
   - 按祖先費率從高到低
   - 確保父交易在子交易之前

Coinbase 交易

每個區塊的第一筆交易是 coinbase,創造新幣:

Coinbase 交易結構:
├── inputs: 1 個
│   ├── txid: 00000...0000 (32 個零)
│   ├── vout: 0xffffffff
│   └── scriptSig: 區塊高度 + 額外 nonce + 任意數據
└── outputs: 多個
    ├── 區塊獎勵地址
    └── witness commitment (SegWit)

Coinbase 腳本

scriptSig 要求 (BIP-34):
- 必須以區塊高度開頭
- 最多 100 bytes

範例:
03 (高度編碼長度)
40 0d 0c (區塊高度: 789,824)
... (額外 nonce 和礦池標識)

Witness Commitment

SegWit 區塊要求在 coinbase 包含 witness commitment:

OP_RETURN
6a24aa21a9ed  (固定前綴)
<32 bytes commitment>

commitment = SHA256(SHA256(
  witness_root || witness_reserved_value
))

Stratum 協議

礦池與礦機之間的通訊協議:

連接和訂閱

// 礦機 → 礦池: 訂閱
{"id": 1, "method": "mining.subscribe", "params": ["miner/1.0"]}

// 礦池 → 礦機: 響應
{
  "id": 1,
  "result": [
    [
      ["mining.set_difficulty", "subscription_id"],
      ["mining.notify", "subscription_id"]
    ],
    "extranonce1",
    4  // extranonce2 大小
  ],
  "error": null
}

授權

// 礦機 → 礦池: 授權
{"id": 2, "method": "mining.authorize", "params": ["worker.1", "password"]}

// 礦池 → 礦機
{"id": 2, "result": true, "error": null}

工作分配

// 礦池 → 礦機: 新工作
{
  "id": null,
  "method": "mining.notify",
  "params": [
    "job_id",
    "prevhash",
    "coinbase1",
    "coinbase2",
    ["merkle_branch_1", "merkle_branch_2", ...],
    "version",
    "nbits",
    "ntime",
    true  // 清除舊工作
  ]
}

提交份額

// 礦機 → 礦池: 提交
{
  "id": 3,
  "method": "mining.submit",
  "params": [
    "worker.1",
    "job_id",
    "extranonce2",
    "ntime",
    "nonce"
  ]
}

// 礦池 → 礦機
{"id": 3, "result": true, "error": null}

Stratum V2

新一代礦池協議,改進安全性和去中心化:

  • 加密通訊:使用 AEAD 加密
  • 二進制協議:比 JSON 更高效
  • Job Negotiation:礦工可以構建自己的區塊模板
  • 更低延遲:優化的消息格式

TypeScript 實作

計算區塊雜湊

import * as crypto from 'crypto';

interface BlockHeader {
  version: number;
  prevBlockHash: string;
  merkleRoot: string;
  timestamp: number;
  bits: number;
  nonce: number;
}

function serializeHeader(header: BlockHeader): Buffer {
  const buffer = Buffer.alloc(80);

  buffer.writeInt32LE(header.version, 0);
  Buffer.from(header.prevBlockHash, 'hex').reverse().copy(buffer, 4);
  Buffer.from(header.merkleRoot, 'hex').reverse().copy(buffer, 36);
  buffer.writeUInt32LE(header.timestamp, 68);
  buffer.writeUInt32LE(header.bits, 72);
  buffer.writeUInt32LE(header.nonce, 76);

  return buffer;
}

function hashBlock(header: BlockHeader): string {
  const serialized = serializeHeader(header);
  const hash1 = crypto.createHash('sha256').update(serialized).digest();
  const hash2 = crypto.createHash('sha256').update(hash1).digest();
  return hash2.reverse().toString('hex');
}

// 範例
const header: BlockHeader = {
  version: 0x20000000,
  prevBlockHash: '00000000000000000002...',
  merkleRoot: 'abc123...',
  timestamp: 1700000000,
  bits: 0x17034219,
  nonce: 0,
};

const blockHash = hashBlock(header);
console.log('Block hash:', blockHash);

難度計算

// bits 轉目標值
function bitsToTarget(bits: number): bigint {
  const exponent = (bits >> 24) & 0xff;
  const mantissa = bits & 0x007fffff;

  if (exponent <= 3) {
    return BigInt(mantissa >> (8 * (3 - exponent)));
  } else {
    return BigInt(mantissa) << BigInt(8 * (exponent - 3));
  }
}

// 計算難度
function calculateDifficulty(bits: number): number {
  const maxTarget = bitsToTarget(0x1d00ffff);
  const currentTarget = bitsToTarget(bits);
  return Number(maxTarget) / Number(currentTarget);
}

// 範例
const bits = 0x17034219;
const target = bitsToTarget(bits);
const difficulty = calculateDifficulty(bits);

console.log('Target:', target.toString(16));
console.log('Difficulty:', difficulty.toExponential(2));

建立 Merkle 樹

function doubleSha256(data: Buffer): Buffer {
  const hash1 = crypto.createHash('sha256').update(data).digest();
  return crypto.createHash('sha256').update(hash1).digest();
}

function buildMerkleRoot(txids: string[]): string {
  if (txids.length === 0) {
    throw new Error('No transactions');
  }

  // 轉換為 Buffer 並反轉(內部位元組順序)
  let hashes = txids.map((txid) =>
    Buffer.from(txid, 'hex').reverse()
  );

  while (hashes.length > 1) {
    const newHashes: Buffer[] = [];

    // 如果奇數,複製最後一個
    if (hashes.length % 2 === 1) {
      hashes.push(hashes[hashes.length - 1]);
    }

    for (let i = 0; i < hashes.length; i += 2) {
      const combined = Buffer.concat([hashes[i], hashes[i + 1]]);
      newHashes.push(doubleSha256(combined));
    }

    hashes = newHashes;
  }

  return hashes[0].reverse().toString('hex');
}

簡單挖礦模擬

async function simpleMining(
  header: BlockHeader,
  targetBits: number
): Promise<{ nonce: number; hash: string } | null> {
  const target = bitsToTarget(targetBits);

  for (let nonce = 0; nonce < 0xffffffff; nonce++) {
    header.nonce = nonce;
    const hash = hashBlock(header);
    const hashBigInt = BigInt('0x' + hash);

    if (hashBigInt < target) {
      return { nonce, hash };
    }

    // 每 100 萬次報告進度
    if (nonce % 1000000 === 0) {
      console.log(`Tried ${nonce} nonces...`);
    }
  }

  return null;
}

// 注意: 這只是演示,真正的挖礦需要
// - 調整 coinbase 的 extranonce
// - 使用 ASIC 硬體
// - 連接礦池

挖礦 RPC

常用命令

# 獲取挖礦資訊
bitcoin-cli getmininginfo

# 結果
{
  "blocks": 800000,
  "difficulty": 55621444139429.57,
  "networkhashps": 4.5e+20,
  "pooledtx": 15000,
  "chain": "main"
}

# 獲取網路雜湊率
bitcoin-cli getnetworkhashps

# 估算區塊數量
bitcoin-cli getnetworkhashps 120 -1  # 最近 120 區塊

# 提交區塊
bitcoin-cli submitblock "hexdata..."

挖礦經濟

獎勵時間表

減半次數 區塊範圍 獎勵 年份
0 0 - 209,999 50 BTC 2009-2012
1 210,000 - 419,999 25 BTC 2012-2016
2 420,000 - 629,999 12.5 BTC 2016-2020
3 630,000 - 839,999 6.25 BTC 2020-2024
4 840,000 - 1,049,999 3.125 BTC 2024-2028

收益計算

interface MiningProfitability {
  hashrate: number;      // TH/s
  powerConsumption: number; // Watts
  electricityCost: number;  // $/kWh
  poolFee: number;       // 比例 (0.02 = 2%)
  btcPrice: number;      // USD
}

function calculateDailyProfit(
  params: MiningProfitability,
  networkHashrate: number,  // EH/s
  blockReward: number
): { revenue: number; cost: number; profit: number } {
  const blocksPerDay = 144;
  const totalRewardPerDay = blocksPerDay * blockReward;

  // 份額 = 我的雜湊率 / 全網雜湊率
  const share = (params.hashrate * 1e12) / (networkHashrate * 1e18);

  // 日收入
  const btcPerDay = totalRewardPerDay * share * (1 - params.poolFee);
  const revenue = btcPerDay * params.btcPrice;

  // 電費
  const kwhPerDay = (params.powerConsumption * 24) / 1000;
  const cost = kwhPerDay * params.electricityCost;

  return {
    revenue,
    cost,
    profit: revenue - cost,
  };
}

最佳實踐

  • 使用 Stratum V2:更安全、更去中心化
  • 分散礦池:避免算力集中
  • 監控難度:預測收益變化
  • 優化電力:選擇低電費地區
  • 備份配置:保護礦工身份和設定

相關資源

已複製連結
已複製到剪貼簿