跳至主要內容
高級

LevelDB

深入了解 Bitcoin Core 使用的 LevelDB 鍵值資料庫,儲存區塊索引和 UTXO 集。

12 分鐘

什麼是 LevelDB?

LevelDB 是 Google 開發的快速鍵值儲存庫,Bitcoin Core 用它來儲存區塊索引和 UTXO 集(chainstate)。 它使用 LSM-tree(Log-Structured Merge-tree)結構,針對寫入密集型工作負載優化。

Bitcoin Core 中的 LevelDB

blocks/index/

  • • 區塊頭索引
  • • 區塊在 blk*.dat 中的位置
  • • 區塊元數據

chainstate/

  • • UTXO 集
  • • 當前最佳區塊哈希
  • • 數據庫版本

數據結構

區塊索引格式

// blocks/index 的鍵值格式

// 區塊索引條目
// Key: 'b' + block_hash (33 bytes)
// Value: CDiskBlockIndex 序列化
interface BlockIndexEntry {
  version: number;
  height: number;
  status: number;        // 驗證狀態
  nTx: number;           // 交易數量
  nFile: number;         // blk*.dat 檔案編號
  nDataPos: number;      // 區塊在檔案中的位置
  nUndoPos: number;      // undo 數據位置
  header: BlockHeader;   // 區塊頭
}

// 檔案資訊
// Key: 'f' + file_number (5 bytes)
// Value: 檔案統計資訊
interface FileInfo {
  blocks: number;        // 區塊數量
  size: number;          // 檔案大小
  undoSize: number;      // undo 檔案大小
  heightFirst: number;   // 最低區塊高度
  heightLast: number;    // 最高區塊高度
  timeFirst: number;     // 最早時間戳
  timeLast: number;      // 最晚時間戳
}

// 最後一個區塊檔案編號
// Key: 'l' (1 byte)
// Value: 最後的檔案編號

// 重新索引標記
// Key: 'R' (1 byte)
// Value: 1 表示需要重新索引

// 區塊標誌
// Key: 'F' + flag_name
// Value: 布林值

Chainstate 格式

// chainstate 的鍵值格式

// UTXO 條目
// Key: 'C' + txid (33 bytes)
// Value: 壓縮的 UTXO 數據
interface UTXOEntry {
  // Varint 編碼的高度和 coinbase 標誌
  // 最低位 = coinbase,其餘位 = height
  heightAndCoinbase: number;

  // 每個未花費輸出
  outputs: Array<{
    // Varint: 與上一個輸出的索引差
    // 0 = 輸出 0,1 = 輸出 1,...
    // 如果輸出已花費則跳過
    index: number;

    // 壓縮的金額(特殊編碼節省空間)
    amount: CompressedAmount;

    // 壓縮的腳本
    script: CompressedScript;
  }>;
}

// 最佳區塊哈希
// Key: 'B' (1 byte)
// Value: 32 字節區塊哈希

// 數據庫版本
// Key: 'D' + 'b' (2 bytes)
// Value: 版本號

// Obfuscation key
// Key: 14 bytes (固定)
// Value: XOR 混淆密鑰

LSM-Tree 結構

LevelDB LSM-Tree 結構:

┌─────────────────────────────────────────────────────┐
│                    MemTable                          │
│            (記憶體中的寫入緩衝)                      │
│                  ↓ 滿了後刷新                         │
├─────────────────────────────────────────────────────┤
│                 Immutable MemTable                   │
│               (等待寫入磁碟)                         │
│                  ↓ 寫入                              │
├─────────────────────────────────────────────────────┤
│  Level 0: SSTable SSTable SSTable ...               │
│           (剛從 MemTable 刷新,可能重疊)             │
│                  ↓ 合併                              │
├─────────────────────────────────────────────────────┤
│  Level 1: SSTable SSTable SSTable ...               │
│           (已排序,不重疊)                           │
│                  ↓ 合併                              │
├─────────────────────────────────────────────────────┤
│  Level 2: SSTable SSTable SSTable SSTable ...       │
│                  ↓                                   │
│  ...更多層級...                                      │
└─────────────────────────────────────────────────────┘

SSTable = Sorted String Table(已排序的字符串表)

讀寫特性

操作 特性
寫入 O(1) - 先寫入 MemTable
讀取 O(log N) - 可能需要查詢多個層級
範圍掃描 高效 - 數據已排序
刪除 寫入 tombstone 標記

數據混淆

Bitcoin Core 使用 XOR 混淆來防止殺毒軟體誤報(區塊鏈數據可能包含看起來像惡意內容的模式):

class ObfuscatedDB {
  private obfuscateKey: Buffer;

  constructor() {
    // 首次創建時生成隨機密鑰
    // 或從數據庫讀取現有密鑰
    this.obfuscateKey = this.getOrCreateKey();
  }

  write(key: Buffer, value: Buffer): void {
    const obfuscated = this.xor(value, this.obfuscateKey);
    this.db.put(key, obfuscated);
  }

  read(key: Buffer): Buffer {
    const obfuscated = this.db.get(key);
    return this.xor(obfuscated, this.obfuscateKey);
  }

  private xor(data: Buffer, key: Buffer): Buffer {
    const result = Buffer.alloc(data.length);
    for (let i = 0; i < data.length; i++) {
      result[i] = data[i] ^ key[i % key.length];
    }
    return result;
  }
}

維護與診斷

# 數據庫位置
~/.bitcoin/blocks/index/    # 區塊索引
~/.bitcoin/chainstate/      # UTXO 集

# 檢查數據庫大小
du -sh ~/.bitcoin/blocks/index/
du -sh ~/.bitcoin/chainstate/

# 常見錯誤處理
# "Corruption: block checksum mismatch"
# "LevelDB read failure"
# → 通常需要 -reindex 或 -reindex-chainstate

# 重建區塊索引
bitcoind -reindex

# 僅重建 chainstate
bitcoind -reindex-chainstate

# 增加 dbcache 可以減少 LevelDB I/O
bitcoind -dbcache=4096

警告: 不要直接修改 LevelDB 文件。數據庫損壞可能導致節點無法啟動。 如果懷疑數據庫損壞,使用 -reindex 重建。

總結

  • 用途:儲存區塊索引和 UTXO 集
  • LSM-Tree:針對寫入密集型工作優化
  • 混淆:XOR 加密防止殺毒軟體誤報
  • 維護:損壞時使用 -reindex 重建
已複製連結
已複製到剪貼簿