跳至主要內容
進階

Mempool

比特幣交易池:未確認交易的管理、手續費估算和 RBF 機制

20 分鐘

概述

Mempool(Memory Pool)是每個比特幣節點維護的未確認交易池。 當交易被廣播到網路但尚未被打包進區塊時,它們會暫存在 mempool 中。 礦工從 mempool 選擇交易來構建下一個區塊。

關鍵概念: 每個節點的 mempool 可能不同。沒有「全局 mempool」的概念。 交易可能在某些節點的 mempool 中,而不在其他節點中。

Mempool 結構

交易條目

每個 mempool 條目包含:

  • 交易數據:完整的序列化交易
  • 手續費:交易支付的總手續費
  • 虛擬大小:考慮 SegWit 折扣後的大小
  • 進入時間:交易進入 mempool 的時間
  • 祖先資訊:依賴的未確認交易
  • 後代資訊:依賴此交易的其他交易

索引結構

Mempool 維護多個索引:

1. txid 索引
   - 快速查找特定交易
   - O(1) 查找時間

2. 祖先費率索引
   - 用於挖礦排序
   - 考慮整個交易包的費率

3. 時間索引
   - 用於清理過期交易
   - 追蹤交易在 mempool 中的時間

4. 後代索引
   - 追蹤交易依賴關係
   - 用於 RBF 和 CPFP

手續費估算

費率單位

sat/vB (satoshis per virtual byte)
- vB = 虛擬字節,考慮 SegWit 折扣
- 傳統交易: 1 byte = 1 vB
- SegWit witness: 1 byte = 0.25 vB

範例:
- 傳統 P2PKH: ~226 bytes = 226 vB
- SegWit P2WPKH: ~141 bytes, 110 vB
- 費率 10 sat/vB, P2WPKH 手續費 ≈ 1,100 sat

估算策略

確認目標 典型費率 說明
1 區塊 下一區塊確認,需要高於 mempool 中大多數交易
6 區塊 約 1 小時內確認
144 區塊 約 1 天內確認,可以使用較低費率

RPC 命令

# 估算手續費(目標 6 區塊確認)
bitcoin-cli estimatesmartfee 6

# 結果
{
  "feerate": 0.00012500,  # BTC/kB
  "blocks": 6
}

# 轉換為 sat/vB: 0.00012500 * 100000000 / 1000 = 12.5 sat/vB

RBF (Replace-By-Fee)

RBF 允許用更高手續費的新交易替換 mempool 中的舊交易。 這是加速交易確認的主要方法。

BIP-125 規則

  1. 原交易必須標記為可替換(nSequence < 0xfffffffe)
  2. 替換交易必須支付更高的絕對手續費
  3. 替換交易的費率必須高於原交易
  4. 替換交易不能添加新的未確認輸入
  5. 替換交易必須支付額外費用覆蓋被替換交易的大小

信號 RBF

// 在交易輸入中設置 nSequence
interface TxInput {
  txid: string;
  vout: number;
  sequence: number;  // < 0xfffffffe 表示可替換
}

// 標記為可替換
const replaceableInput: TxInput = {
  txid: '...',
  vout: 0,
  sequence: 0xfffffffd,  // 可替換
};

// 不可替換(預設)
const finalInput: TxInput = {
  txid: '...',
  vout: 0,
  sequence: 0xffffffff,  // 最終
};

執行替換

# 使用 bitcoin-cli 進行 RBF
# 1. 創建原交易(標記可替換)
bitcoin-cli createrawtransaction \
  '[{"txid":"...", "vout":0, "sequence":4294967293}]' \
  '{"bc1q...": 0.001}'

# 2. 簽名並發送
bitcoin-cli signrawtransactionwithwallet <hex>
bitcoin-cli sendrawtransaction <signed_hex>

# 3. 創建替換交易(更高手續費)
bitcoin-cli createrawtransaction \
  '[{"txid":"...", "vout":0, "sequence":4294967293}]' \
  '{"bc1q...": 0.0009}'  # 更少輸出 = 更高手續費

# 4. 簽名並發送替換交易
bitcoin-cli signrawtransactionwithwallet <new_hex>
bitcoin-cli sendrawtransaction <new_signed_hex>

CPFP (Child-Pays-For-Parent)

當無法使用 RBF 時(交易未標記可替換或不控制輸入), 可以創建一個高手續費的子交易來「拉動」父交易。

CPFP 原理:

父交易 (低手續費)
├── 輸出 0: 給接收者
└── 輸出 1: 找零給自己

子交易 (高手續費)
└── 輸入: 父交易的找零輸出

礦工會將父子交易視為一個包(package),
如果組合費率夠高,就會一起打包。

計算組合費率

interface Transaction {
  vsize: number;
  fee: number;
}

function calculatePackageRate(
  parent: Transaction,
  child: Transaction
): number {
  const totalFee = parent.fee + child.fee;
  const totalVsize = parent.vsize + child.vsize;
  return totalFee / totalVsize;
}

// 範例
const parent = { vsize: 200, fee: 200 };   // 1 sat/vB
const child = { vsize: 150, fee: 3000 };   // 20 sat/vB

const packageRate = calculatePackageRate(parent, child);
// (200 + 3000) / (200 + 150) = 9.14 sat/vB

Mempool 政策

接受規則

規則 預設值 說明
最小費率 1 sat/vB 低於此費率的交易被拒絕
最大大小 100 kB 標準交易大小上限
祖先限制 25 交易 最大未確認祖先數量
後代限制 25 交易 最大未確認後代數量
塵埃限制 546 sat 輸出金額下限

配置選項

# bitcoin.conf

# Mempool 大小限制(MB)
maxmempool=300

# 最小交易費率(BTC/kB)
minrelaytxfee=0.00001

# 允許 RBF
walletrbf=1

# 驅逐期限(小時)
mempoolexpiry=336

交易驅逐

當 mempool 達到大小限制時,低費率的交易會被驅逐:

  1. 按祖先費率排序所有交易
  2. 從最低費率開始驅逐
  3. 驅逐時也移除所有後代交易
  4. 直到 mempool 回到目標大小以下
驅逐優先級(從低到高):
1. 費率最低的交易
2. 在 mempool 中時間最長的交易
3. 有大量後代的交易(驅逐後節省更多空間)

TypeScript 實作

查詢 Mempool 資訊

import { JSONRPCClient } from 'json-rpc-2.0';

const client = new JSONRPCClient((request) =>
  fetch('http://localhost:8332', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Basic ' + btoa('user:password'),
    },
    body: JSON.stringify(request),
  }).then((response) => response.json())
);

interface MempoolInfo {
  loaded: boolean;
  size: number;           // 交易數量
  bytes: number;          // 總大小
  usage: number;          // 記憶體使用
  maxmempool: number;     // 最大大小
  mempoolminfee: number;  // 最低費率
  minrelaytxfee: number;  // 最低中繼費率
}

async function getMempoolInfo(): Promise<MempoolInfo> {
  return client.request('getmempoolinfo', []);
}

// 獲取 mempool 中所有交易 ID
async function getRawMempool(): Promise<string[]> {
  return client.request('getrawmempool', [false]);
}

// 獲取詳細資訊
interface MempoolEntry {
  vsize: number;
  weight: number;
  fee: number;
  modifiedfee: number;
  time: number;
  height: number;
  descendantcount: number;
  descendantsize: number;
  descendantfees: number;
  ancestorcount: number;
  ancestorsize: number;
  ancestorfees: number;
  depends: string[];
  spentby: string[];
}

async function getMempoolEntry(txid: string): Promise<MempoolEntry> {
  return client.request('getmempoolentry', [txid]);
}

手續費估算

interface FeeEstimate {
  feerate?: number;  // BTC/kB
  errors?: string[];
  blocks: number;
}

async function estimateFee(
  blocks: number,
  mode: 'ECONOMICAL' | 'CONSERVATIVE' = 'ECONOMICAL'
): Promise<number> {
  const result: FeeEstimate = await client.request('estimatesmartfee', [
    blocks,
    mode,
  ]);

  if (!result.feerate) {
    throw new Error(result.errors?.join(', ') || 'Cannot estimate fee');
  }

  // 轉換為 sat/vB
  return Math.ceil(result.feerate * 100000);
}

// 根據優先級獲取推薦費率
async function getRecommendedFees(): Promise<{
  fastest: number;
  halfHour: number;
  hour: number;
  economy: number;
}> {
  const [fastest, halfHour, hour, economy] = await Promise.all([
    estimateFee(1),
    estimateFee(3),
    estimateFee(6),
    estimateFee(144),
  ]);

  return { fastest, halfHour, hour, economy };
}

RBF 交易替換

import * as bitcoin from 'bitcoinjs-lib';

interface RBFOptions {
  originalTxHex: string;
  newFeeRate: number;  // sat/vB
  privateKey: Buffer;
}

async function createRBFTransaction(options: RBFOptions): Promise<string> {
  const tx = bitcoin.Transaction.fromHex(options.originalTxHex);

  // 確保輸入標記為可替換
  for (const input of tx.ins) {
    if (input.sequence === 0xffffffff) {
      throw new Error('Transaction is not marked as replaceable');
    }
  }

  // 計算新的手續費
  const currentFee = await calculateTxFee(tx);
  const vsize = tx.virtualSize();
  const newFee = vsize * options.newFeeRate;

  if (newFee <= currentFee) {
    throw new Error('New fee must be higher than current fee');
  }

  // 調整輸出金額以增加手續費
  const feeIncrease = newFee - currentFee;
  const lastOutput = tx.outs[tx.outs.length - 1];

  if (lastOutput.value < feeIncrease) {
    throw new Error('Insufficient funds to increase fee');
  }

  lastOutput.value -= feeIncrease;

  // 重新簽名
  const keyPair = bitcoin.ECPair.fromPrivateKey(options.privateKey);
  // ... 簽名邏輯

  return tx.toHex();
}

Mempool 監控

訂閱新交易

import { WebSocket } from 'ws';

// 使用 ZMQ 訂閱(需要 Bitcoin Core 啟用 ZMQ)
function subscribeToNewTransactions(
  zmqEndpoint: string,
  onTransaction: (txid: string, rawTx: Buffer) => void
) {
  const zmq = require('zeromq');
  const socket = zmq.socket('sub');

  socket.connect(zmqEndpoint);
  socket.subscribe('rawtx');
  socket.subscribe('hashtx');

  socket.on('message', (topic: Buffer, message: Buffer) => {
    const topicStr = topic.toString();

    if (topicStr === 'hashtx') {
      const txid = message.toString('hex');
      console.log('New transaction:', txid);
    } else if (topicStr === 'rawtx') {
      const tx = bitcoin.Transaction.fromBuffer(message);
      onTransaction(tx.getId(), message);
    }
  });

  return () => socket.close();
}

// Bitcoin Core 配置
// zmqpubrawtx=tcp://127.0.0.1:28332
// zmqpubhashtx=tcp://127.0.0.1:28332

最佳實踐

  • 始終使用 RBF:標記交易為可替換,保留加速選項
  • 適當的費率估算:根據確認時間需求選擇費率
  • 監控 mempool 狀態:高峰期調整費率策略
  • 批次交易:多個支付合併為一個交易節省費用
  • 使用 SegWit:降低交易虛擬大小

相關資源

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