跳至主要內容
進階

Block Files

深入了解 Bitcoin Core 的區塊文件管理,blk*.dat 和 rev*.dat 的結構與存儲。

10 分鐘

區塊文件概覽

Bitcoin Core 將區塊鏈數據存儲在 blocks/ 目錄中。 區塊數據存儲在 blk*.dat 文件中,而對應的撤銷數據存儲在 rev*.dat 文件中。

目錄結構

~/.bitcoin/blocks/
├── blk00000.dat      # 區塊數據文件 #0
├── blk00001.dat      # 區塊數據文件 #1
├── blk00002.dat      # ...
├── ...
├── blk04000.dat      # 最新的區塊文件(示例)
│
├── rev00000.dat      # 撤銷數據文件 #0
├── rev00001.dat      # 撤銷數據文件 #1
├── ...
│
└── index/            # 區塊索引(LevelDB)
    ├── CURRENT
    ├── LOCK
    ├── LOG
    ├── MANIFEST-*
    └── *.ldb

文件大小:
- 每個 blk*.dat 文件最大約 128 MB(可配置)
- 總區塊鏈大小約 500+ GB(2024 年)

blk*.dat 格式

blk*.dat 文件格式:

┌────────────────────────────────────────────────────────────┐
│ Block 1                                                    │
│ ├── 4 bytes: Magic Number (0xD9B4BEF9 for mainnet)        │
│ ├── 4 bytes: Block Size                                   │
│ └── N bytes: Block Data                                   │
│      ├── 80 bytes: Block Header                           │
│      ├── VarInt: Transaction Count                        │
│      └── Transactions...                                  │
├────────────────────────────────────────────────────────────┤
│ Block 2                                                    │
│ ├── 4 bytes: Magic Number                                 │
│ ├── 4 bytes: Block Size                                   │
│ └── N bytes: Block Data                                   │
├────────────────────────────────────────────────────────────┤
│ ...                                                        │
└────────────────────────────────────────────────────────────┘

Magic Numbers:
- Mainnet:  0xD9B4BEF9
- Testnet:  0x0709110B
- Signet:   0x40CF030A
- Regtest:  0xDAB5BFFA
// 讀取 blk 文件
interface BlockFileEntry {
  magic: Buffer;      // 4 bytes
  size: number;       // 4 bytes
  blockData: Buffer;  // size bytes
}

function readBlockFile(path: string): Block[] {
  const blocks: Block[] = [];
  const file = fs.readFileSync(path);
  let offset = 0;

  while (offset < file.length) {
    // 讀取 magic number
    const magic = file.slice(offset, offset + 4);
    offset += 4;

    // 驗證 magic
    if (!magic.equals(MAINNET_MAGIC)) {
      break;  // 文件結束或損壞
    }

    // 讀取區塊大小
    const size = file.readUInt32LE(offset);
    offset += 4;

    // 讀取區塊數據
    const blockData = file.slice(offset, offset + size);
    offset += size;

    // 解析區塊
    blocks.push(Block.deserialize(blockData));
  }

  return blocks;
}

rev*.dat 格式

rev*.dat 文件格式(Undo Data):

┌────────────────────────────────────────────────────────────┐
│ Block Undo 1                                               │
│ ├── 4 bytes: Magic Number                                 │
│ ├── 4 bytes: Undo Size                                    │
│ └── Undo Data                                             │
│      ├── VarInt: 交易數量(不含 coinbase)                  │
│      └── 每個交易的被花費輸出                              │
│           ├── VarInt: 輸入數量                            │
│           └── 每個輸入的 Coin                             │
│                ├── 高度 + coinbase 標誌(壓縮)            │
│                ├── 金額(壓縮)                            │
│                └── scriptPubKey(壓縮)                    │
├────────────────────────────────────────────────────────────┤
│ Block Undo 2                                               │
│ └── ...                                                    │
└────────────────────────────────────────────────────────────┘

用途:區塊鏈重組時恢復被花費的 UTXO

區塊索引

// blocks/index/ 存儲的索引數據
interface BlockIndex {
  // 'b' + blockhash -> CBlockIndex
  blockIndex: Map;

  // 'f' + fileNumber -> CBlockFileInfo
  fileInfo: Map;

  // 'l' -> lastBlockFile number
  lastBlockFile: number;

  // 'R' -> reindexing flag
  reindexing: boolean;

  // 'F' + flag -> boolean
  flags: Map;
}

interface CBlockIndex {
  version: number;
  hashPrev: Buffer;
  hashMerkleRoot: Buffer;
  nTime: number;
  nBits: number;
  nNonce: number;
  height: number;
  nFile: number;        // blk 文件編號
  nDataPos: number;     // 區塊數據在文件中的位置
  nUndoPos: number;     // undo 數據位置
  nChainWork: bigint;
  nTx: number;
  nStatus: number;
}

interface CBlockFileInfo {
  nBlocks: number;      // 文件中的區塊數量
  nSize: number;        // 文件大小
  nUndoSize: number;    // undo 文件大小
  nHeightFirst: number; // 最低區塊高度
  nHeightLast: number;  // 最高區塊高度
  nTimeFirst: number;   // 最早時間戳
  nTimeLast: number;    // 最晚時間戳
}

文件管理

# 查看區塊文件
ls -lh ~/.bitcoin/blocks/blk*.dat | head -10

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

# 查看文件數量
ls ~/.bitcoin/blocks/blk*.dat | wc -l

# 查看最新的區塊文件
ls -lt ~/.bitcoin/blocks/blk*.dat | head -1

# 使用 hexdump 查看文件頭
hexdump -C ~/.bitcoin/blocks/blk00000.dat | head -5

# 輸出(mainnet magic: F9 BE B4 D9)
# 00000000  f9 be b4 d9 1d 01 00 00  01 00 00 00 00 00 00 00

# 查看區塊文件信息(通過 RPC)
bitcoin-cli getblockchaininfo | jq '.size_on_disk'
// 區塊文件分配策略
class BlockFileManager {
  // 最大文件大小(約 128 MB)
  private readonly MAX_BLOCKFILE_SIZE = 0x8000000;

  // 當前寫入的文件
  private currentFile: number = 0;
  private currentPos: number = 0;

  // 分配空間寫入區塊
  allocate(blockSize: number): { file: number; pos: number } {
    // 檢查當前文件是否有足夠空間
    if (this.currentPos + blockSize > this.MAX_BLOCKFILE_SIZE) {
      // 切換到新文件
      this.currentFile++;
      this.currentPos = 0;
    }

    const pos = this.currentPos;
    this.currentPos += blockSize;

    return {
      file: this.currentFile,
      pos: pos,
    };
  }

  // 獲取文件路徑
  getBlockFilePath(fileNumber: number): string {
    const name = `blk${fileNumber.toString().padStart(5, '0')}.dat`;
    return path.join(this.dataDir, 'blocks', name);
  }
}

Pruning 模式

# 啟用 pruning(保留 550 MB)
bitcoind -prune=550

# 或在配置文件中
# bitcoin.conf
prune=550

# Pruning 工作方式:
# 1. 保留最近的區塊文件(約 550 MB)
# 2. 刪除舊的 blk*.dat 和 rev*.dat
# 3. 保留完整的 chainstate(UTXO 集)
# 4. 保留區塊索引(可以知道區塊存在但無數據)

# 查看 pruning 狀態
bitcoin-cli getblockchaininfo | jq '{pruned, pruneheight, size_on_disk}'

# 輸出示例
{
  "pruned": true,
  "pruneheight": 700000,
  "size_on_disk": 600000000
}

注意: Pruned 節點無法為其他節點提供歷史區塊,也無法使用 txindex

重建索引

# 重建所有索引(從 blk 文件重新驗證)
bitcoind -reindex

# 只重建 chainstate(不重新驗證區塊)
bitcoind -reindex-chainstate

# 工作流程:
# -reindex:
#   1. 刪除 blocks/index/
#   2. 刪除 chainstate/
#   3. 重新讀取所有 blk*.dat
#   4. 重新驗證所有區塊
#   5. 重建所有索引

# -reindex-chainstate:
#   1. 保留 blocks/index/
#   2. 刪除 chainstate/
#   3. 使用索引重建 UTXO 集
#   4. 比 -reindex 快很多

實用工具

# Python 腳本:讀取 blk 文件
import struct

MAGIC = bytes.fromhex('F9BEB4D9')  # mainnet

def read_blocks(filepath):
    with open(filepath, 'rb') as f:
        while True:
            # 讀取 magic
            magic = f.read(4)
            if len(magic) < 4 or magic != MAGIC:
                break

            # 讀取大小
            size = struct.unpack('

總結

  • blk*.dat:原始區塊數據,約 128 MB/文件
  • rev*.dat:撤銷數據,用於區塊重組
  • index/:LevelDB 區塊索引
  • Pruning:可刪除舊區塊文件節省空間
已複製連結
已複製到剪貼簿