跳至主要內容
進階

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 交易可以被替換,不應信任未確認狀態
已複製連結
已複製到剪貼簿