進階
Replace-by-Fee (RBF)
深入了解 BIP-125 Replace-by-Fee 機制,如何透過提高手續費來替換未確認交易。
12 分鐘
什麼是 RBF?
Replace-by-Fee(RBF)是 BIP-125 定義的交易替換機制,允許發送者在交易未確認前, 透過支付更高的手續費來替換原始交易。這解決了手續費估算錯誤導致交易長時間無法確認的問題。
RBF 的用途
手續費提升
- • 交易卡在 mempool 時加速確認
- • 手續費估算錯誤後的補救
- • 應對網路擁堵
交易修改
- • 更改收款地址(發送前)
- • 添加或移除輸出
- • 合併多筆付款
BIP-125 規則
RBF 信號
交易必須明確表示可被替換。這透過設定輸入的 sequence number 來實現:
RBF 信號條件:
任一輸入的 nSequence < 0xFFFFFFFE (4294967294)
常見設定:
- 0xFFFFFFFD (4294967293) - 啟用 RBF,無時間鎖
- 0xFFFFFFFE (4294967294) - 禁用 RBF,無時間鎖
- 0xFFFFFFFF (4294967295) - 禁用 RBF,禁用 nLockTime
繼承性:
如果交易 A 是 RBF,那麼花費 A 輸出的交易 B 也被視為可替換
(即使 B 本身沒有 RBF 信號) 替換規則
- 1 原始交易必須有 RBF 信號
至少一個輸入的 nSequence < 0xFFFFFFFE
- 2 替換交易必須花費相同的輸入
至少花費一個與原始交易相同的輸入
- 3 更高的絕對手續費
替換交易的手續費必須高於被替換的所有交易總和
- 4 支付中繼費用
額外手續費至少要覆蓋替換交易的最小中繼費用
- 5 替換數量限制
最多替換 100 個交易(包括子交易)
實現細節
創建 RBF 交易
interface TxInput {
txid: string;
vout: number;
sequence: number;
}
interface Transaction {
inputs: TxInput[];
outputs: TxOutput[];
locktime: number;
}
// RBF 序號常數
const RBF_SEQUENCE = 0xFFFFFFFD; // 啟用 RBF
const NO_RBF_SEQUENCE = 0xFFFFFFFE; // 禁用 RBF
const FINAL_SEQUENCE = 0xFFFFFFFF; // 完全最終
function createRbfTransaction(
inputs: Array<{ txid: string; vout: number }>,
outputs: TxOutput[]
): Transaction {
return {
inputs: inputs.map(input => ({
...input,
sequence: RBF_SEQUENCE, // 啟用 RBF
})),
outputs,
locktime: 0,
};
}
function isRbfEnabled(tx: Transaction): boolean {
// 任一輸入的 sequence < 0xFFFFFFFE 即為 RBF
return tx.inputs.some(input => input.sequence < NO_RBF_SEQUENCE);
}
// 檢查是否可以替換
function canReplace(
original: Transaction,
replacement: Transaction,
originalFee: number,
replacementFee: number,
minRelayFee: number
): { valid: boolean; reason?: string } {
// 規則 1: 原始交易必須有 RBF 信號
if (!isRbfEnabled(original)) {
return { valid: false, reason: 'Original tx not RBF-enabled' };
}
// 規則 2: 必須花費至少一個相同的輸入
const originalInputs = new Set(
original.inputs.map(i => `${i.txid}:${i.vout}`)
);
const hasConflict = replacement.inputs.some(
i => originalInputs.has(`${i.txid}:${i.vout}`)
);
if (!hasConflict) {
return { valid: false, reason: 'No conflicting inputs' };
}
// 規則 3: 更高的絕對手續費
if (replacementFee <= originalFee) {
return { valid: false, reason: 'Replacement fee not higher' };
}
// 規則 4: 額外費用必須覆蓋中繼成本
const replacementSize = estimateTxSize(replacement);
const minIncrease = replacementSize * minRelayFee;
if (replacementFee - originalFee < minIncrease) {
return { valid: false, reason: 'Fee increase too small' };
}
return { valid: true };
} 使用 Bitcoin CLI
# 創建啟用 RBF 的交易
bitcoin-cli createrawtransaction \
'[{"txid":"abc...","vout":0,"sequence":4294967293}]' \
'{"bc1q...":0.01}'
# 使用錢包創建 RBF 交易
bitcoin-cli -named send \
outputs='{"bc1q...":0.01}' \
options='{"replaceable":true}'
# 替換(提升手續費)已存在的交易
bitcoin-cli bumpfee
# 指定新的手續費率
bitcoin-cli bumpfee '{"fee_rate":50}'
# 查看交易是否可替換
bitcoin-cli gettransaction | jq '.bip125-replaceable'
# 查看 mempool 中的交易
bitcoin-cli getmempoolentry | jq '.bip125-replaceable' Full RBF
什麼是 Full RBF?
Full RBF 允許替換任何未確認交易,即使它沒有明確的 RBF 信號。 這在 Bitcoin Core 24.0 中引入,預設為禁用。
Opt-in RBF vs Full RBF
| 特性 | Opt-in RBF | Full RBF |
|---|---|---|
| 需要信號 | 是 | 否 |
| 預設啟用 | 是 | 否 |
| BIP | BIP-125 | 無正式 BIP |
| 0-conf 安全性 | 取決於信號 | 無保證 |
啟用 Full RBF
# 命令行啟用
bitcoind -mempoolfullrbf=1
# 配置文件
echo "mempoolfullrbf=1" >> ~/.bitcoin/bitcoin.conf
# 查看當前設定
bitcoin-cli getmempoolinfo | jq '.fullrbf' 注意: Full RBF 仍有爭議。它提高了礦工收益和用戶手續費提升的靈活性, 但破壞了某些依賴 0-conf 的商業模式。大多數礦工已啟用 Full RBF。
手續費提升策略
何時提升手續費
interface MempoolStatus {
txid: string;
fee: number;
vsize: number;
ancestorFees: number;
ancestorSize: number;
position: number; // 在 mempool 中的位置
}
async function shouldBumpFee(
tx: MempoolStatus,
targetBlocks: number = 6
): Promise<{ shouldBump: boolean; suggestedRate: number }> {
// 獲取當前費率估算
const estimatedRate = await getFeeEstimate(targetBlocks);
// 計算當前交易的有效費率
const effectiveRate = tx.ancestorFees / tx.ancestorSize;
// 如果當前費率已足夠
if (effectiveRate >= estimatedRate) {
return { shouldBump: false, suggestedRate: 0 };
}
// 建議的新費率(加上一些緩衝)
const suggestedRate = Math.ceil(estimatedRate * 1.1);
return { shouldBump: true, suggestedRate };
}
// 計算提升所需的額外費用
function calculateBumpCost(
currentFee: number,
currentSize: number,
newSize: number, // 替換交易可能大小不同
targetRate: number,
minRelayFee: number
): number {
// 新的總費用
const newTotalFee = newSize * targetRate;
// 額外費用必須至少覆蓋新交易的中繼費用
const minIncrease = newSize * minRelayFee;
// 實際增加的費用
const actualIncrease = newTotalFee - currentFee;
// 取較大值
return Math.max(actualIncrease, minIncrease);
} 安全性考量
雙花風險
RBF 與 0-conf
RBF 交易不應被視為「已收到」——它們可以隨時被替換為支付給不同地址的交易。
- ✗ 不要接受 RBF 交易的 0-conf 付款
- ✗ 不要基於 RBF 交易發貨
- ✓ 等待至少 1 個確認
- ✓ 對大額支付等待更多確認
檢測 RBF 交易
function checkRbfStatus(tx: Transaction): 'rbf' | 'no-rbf' | 'inherited' {
// 直接 RBF 信號
const hasDirectSignal = tx.inputs.some(
input => input.sequence < 0xFFFFFFFE
);
if (hasDirectSignal) {
return 'rbf';
}
// 檢查父交易是否有 RBF(繼承)
// 需要查詢 mempool 中的父交易
for (const input of tx.inputs) {
const parentTx = getMempoolTx(input.txid);
if (parentTx && isRbfEnabled(parentTx)) {
return 'inherited'; // 繼承的 RBF
}
}
return 'no-rbf';
}
// 商家應該這樣檢查
async function isPaymentSafe(txid: string): Promise {
const tx = await getTransaction(txid);
// 檢查確認數
if (tx.confirmations >= 1) {
return true; // 已確認,安全
}
// 檢查 RBF 狀態
const rbfStatus = checkRbfStatus(tx);
if (rbfStatus !== 'no-rbf') {
return false; // RBF 交易,不安全
}
// 即使沒有 RBF,0-conf 仍有風險(Full RBF)
// 商家需要自行評估風險
return false;
} 最佳實踐
✓ 發送者
- • 總是啟用 RBF(除非有特殊理由)
- • 使用合理的初始手續費估算
- • 監控交易確認狀態
- • 必要時使用 bumpfee
✓ 接收者
- • 檢查交易的 RBF 狀態
- • 不信任 RBF 交易的 0-conf
- • 等待適當數量的確認
- • 考慮 Full RBF 的影響
總結
- ✓ 手續費提升:RBF 允許在交易未確認前提高手續費
- ✓ 信號機制:nSequence < 0xFFFFFFFE 表示交易可被替換
- ✓ 繼承性:花費 RBF 輸出的交易也被視為可替換
- ⚠ 0-conf 風險:RBF 交易可以被替換,不應信任未確認狀態
已複製連結