進階
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 規則
- 原交易必須標記為可替換(nSequence < 0xfffffffe)
- 替換交易必須支付更高的絕對手續費
- 替換交易的費率必須高於原交易
- 替換交易不能添加新的未確認輸入
- 替換交易必須支付額外費用覆蓋被替換交易的大小
信號 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 達到大小限制時,低費率的交易會被驅逐:
- 按祖先費率排序所有交易
- 從最低費率開始驅逐
- 驅逐時也移除所有後代交易
- 直到 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:降低交易虛擬大小
相關資源
已複製連結