跳至主要內容
高級

Chain State

深入了解 Bitcoin Core 的鏈狀態管理,UTXO 集合儲存和 chainstate 資料庫。

12 分鐘

什麼是 Chain State?

Chain State(鏈狀態)是 Bitcoin Core 維護的當前區塊鏈狀態,主要包含 UTXO 集合。 它儲存在 chainstate/ 目錄中,使用 LevelDB 資料庫。

資料目錄結構

~/.bitcoin/
├── blocks/
│   ├── blk00000.dat      # 原始區塊數據
│   ├── blk00001.dat
│   ├── rev00000.dat      # Undo 數據
│   └── index/            # 區塊索引(LevelDB)
│
├── chainstate/           # UTXO 集合(LevelDB)
│   ├── CURRENT
│   ├── LOCK
│   ├── LOG
│   ├── MANIFEST-*
│   └── *.ldb
│
└── indexes/              # 可選索引
    ├── txindex/
    └── coinstatsindex/

UTXO 集合

// UTXO 集合中的每個條目
interface UTXOEntry {
  // 鍵:txid + vout
  key: {
    txid: Buffer;  // 32 bytes
    vout: number;  // 4 bytes(varint)
  };

  // 值:Coin 數據
  value: {
    height: number;       // 創建區塊高度
    coinbase: boolean;    // 是否來自 coinbase
    amount: bigint;       // 金額(satoshis)
    scriptPubKey: Buffer; // 輸出腳本
  };
}

// LevelDB 中的鍵格式
// 'C' + txid + vout -> Coin(壓縮格式)

// 統計資訊
interface ChainStateStats {
  height: number;           // 當前高度
  bestblock: Buffer;        // 最佳區塊雜湊
  total_transactions: number;
  total_outputs: number;    // UTXO 數量
  total_amount: bigint;     // 總金額
  size_on_disk: number;     // 磁碟大小
}
# 查看 UTXO 集統計
bitcoin-cli gettxoutsetinfo

# 輸出
{
  "height": 800000,
  "bestblock": "00000000000000000002a7c4c1e48d76c5a37902165a270156b7a8d72728a054",
  "txouts": 89000000,
  "bogosize": 6700000000,
  "muhash": "...",
  "total_amount": 19500000.00000000,
  "transactions": 65000000,
  "disk_size": 5600000000
}

# 查看特定 UTXO
bitcoin-cli gettxout "txid" vout

# 驗證 UTXO 集完整性
bitcoin-cli gettxoutsetinfo "muhash"

Coin Cache

為了提高性能,Bitcoin Core 在記憶體中維護一個 UTXO 快取(dbcache)。

class CCoinsViewCache {
  // 快取的 UTXO
  private cacheCoins: Map;

  // 父視圖(最終是 LevelDB)
  private base: CCoinsView;

  // 快取大小估算
  private cachedCoinsUsage: number;

  // 獲取 UTXO
  async getCoin(outpoint: OutPoint): Promise {
    // 1. 先查快取
    if (this.cacheCoins.has(outpoint)) {
      const coin = this.cacheCoins.get(outpoint);
      return coin.isSpent ? null : coin;
    }

    // 2. 從父視圖獲取
    const coin = await this.base.getCoin(outpoint);
    if (coin) {
      // 加入快取
      this.cacheCoins.set(outpoint, coin);
    }
    return coin;
  }

  // 花費 UTXO
  spendCoin(outpoint: OutPoint): boolean {
    const coin = this.cacheCoins.get(outpoint);
    if (!coin || coin.isSpent) {
      return false;
    }
    coin.isSpent = true;
    return true;
  }

  // 添加 UTXO
  addCoin(outpoint: OutPoint, coin: CCoin): void {
    this.cacheCoins.set(outpoint, coin);
  }

  // 刷新到磁碟
  async flush(): Promise {
    // 批量寫入 LevelDB
    const batch = new LevelDBBatch();

    for (const [outpoint, coin] of this.cacheCoins) {
      if (coin.isSpent) {
        batch.delete(outpoint.key);
      } else if (coin.isDirty) {
        batch.put(outpoint.key, coin.serialize());
      }
    }

    await this.base.writeBatch(batch);
    this.cacheCoins.clear();
    return true;
  }
}

快取層次

UTXO 存取層次:

┌─────────────────────────────────────────┐
│         CCoinsViewCache                 │  ← 記憶體快取(最快)
│         (記憶體中的髒數據)                │     dbcache 設定
├─────────────────────────────────────────┤
│         CCoinsViewCache                 │  ← 中間層快取
│         (乾淨的讀取快取)                  │
├─────────────────────────────────────────┤
│         CCoinsViewDB                    │  ← LevelDB
│         (chainstate/)                   │     磁碟存儲
└─────────────────────────────────────────┘

刷新觸發條件:
1. 快取達到 dbcache 限制
2. 區塊鏈重組
3. 節點關閉
4. 定期刷新(約每小時)

區塊索引

// 區塊索引條目
interface CBlockIndex {
  // 區塊頭數據
  hash: Buffer;           // 區塊雜湊
  hashPrev: Buffer;       // 前一區塊
  height: number;         // 高度
  nTime: number;          // 時間戳
  nBits: number;          // 難度目標
  nNonce: number;         // Nonce

  // 狀態
  nStatus: number;        // 驗證狀態標誌
  nTx: number;            // 交易數量
  nChainWork: bigint;     // 累積工作量

  // 檔案位置
  nFile: number;          // blk 檔案編號
  nDataPos: number;       // 數據偏移
  nUndoPos: number;       // Undo 數據偏移
}

// 狀態標誌
enum BlockStatus {
  BLOCK_VALID_HEADER = 1,      // 區塊頭有效
  BLOCK_VALID_TREE = 2,        // 父區塊已知
  BLOCK_VALID_TRANSACTIONS = 3, // 交易可解析
  BLOCK_VALID_CHAIN = 4,       // 腳本有效
  BLOCK_VALID_SCRIPTS = 5,     // 完全驗證
  BLOCK_HAVE_DATA = 8,         // 有區塊數據
  BLOCK_HAVE_UNDO = 16,        // 有 undo 數據
}
# 區塊索引存儲在 blocks/index/
ls -la ~/.bitcoin/blocks/index/

# 查看區塊資訊
bitcoin-cli getblock "blockhash"

# 查看區塊頭
bitcoin-cli getblockheader "blockhash"

# 查看區塊鏈資訊
bitcoin-cli getblockchaininfo

區塊驗證

class BlockValidator {
  private chainstate: CCoinsViewCache;

  // 驗證並連接區塊
  async connectBlock(block: CBlock, index: CBlockIndex): Promise {
    // 1. 基本檢查
    if (!this.checkBlockHeader(block)) {
      return false;
    }

    // 2. 上下文檢查
    if (!this.contextualCheckBlock(block, index)) {
      return false;
    }

    // 3. 處理每筆交易
    const blockUndo: CBlockUndo = { vtxundo: [] };

    for (let i = 0; i < block.vtx.length; i++) {
      const tx = block.vtx[i];

      if (i === 0) {
        // Coinbase 交易
        this.addCoins(tx, index.height);
      } else {
        // 一般交易
        const txUndo = await this.updateCoins(tx, index.height);
        blockUndo.vtxundo.push(txUndo);
      }
    }

    // 4. 寫入 undo 數據
    await this.writeUndoData(index, blockUndo);

    // 5. 更新鏈狀態
    this.chainstate.setBestBlock(index.hash);

    return true;
  }

  // 更新 UTXO 集
  async updateCoins(tx: CTransaction, height: number): Promise {
    const txUndo: CTxUndo = { vprevout: [] };

    // 花費輸入
    for (const input of tx.vin) {
      const coin = await this.chainstate.getCoin(input.prevout);
      txUndo.vprevout.push(coin);  // 保存用於回滾
      this.chainstate.spendCoin(input.prevout);
    }

    // 添加輸出
    this.addCoins(tx, height);

    return txUndo;
  }
}

鏈重組

class ChainReorganizer {
  // 處理區塊鏈重組
  async reorganize(newTip: CBlockIndex): Promise {
    const currentTip = this.chainstate.getBestBlock();

    // 找到共同祖先
    const fork = this.findForkPoint(currentTip, newTip);

    // 收集要斷開的區塊
    const disconnect: CBlockIndex[] = [];
    let block = currentTip;
    while (block !== fork) {
      disconnect.push(block);
      block = block.prev;
    }

    // 收集要連接的區塊
    const connect: CBlockIndex[] = [];
    block = newTip;
    while (block !== fork) {
      connect.unshift(block);
      block = block.prev;
    }

    // 斷開舊區塊
    for (const blockIndex of disconnect) {
      const undo = await this.readUndoData(blockIndex);
      await this.disconnectBlock(blockIndex, undo);

      // 交易返回 mempool
      this.readdToMempool(blockIndex);
    }

    // 連接新區塊
    for (const blockIndex of connect) {
      const block = await this.readBlock(blockIndex);
      if (!await this.connectBlock(block, blockIndex)) {
        // 連接失敗,回滾
        throw new Error('Failed to connect block');
      }
    }

    console.log(`重組完成: -${disconnect.length} +${connect.length}`);
    return true;
  }
}

維護命令

# 查看 chainstate 大小
du -sh ~/.bitcoin/chainstate/

# 驗證區塊鏈
bitcoin-cli verifychain 4 1000  # 深度 4,檢查 1000 個區塊

# 重建索引(需要重啟)
bitcoind -reindex

# 只重建 chainstate
bitcoind -reindex-chainstate

# 查看快取使用情況
bitcoin-cli getmemoryinfo

# 手動刷新快取
# (無直接命令,關閉節點時自動刷新)

警告: 不要直接修改 chainstate 目錄中的檔案。如果數據損壞,使用 -reindex-chainstate 重建。

總結

  • UTXO 集合:當前所有未花費輸出
  • LevelDB:高效的鍵值存儲
  • 快取層:dbcache 提高性能
  • 重建:使用 -reindex-chainstate 修復損壞
已複製連結
已複製到剪貼簿