跳至主要內容
高級

Package Relay

Package Relay:交易包中繼與驗證機制

16 分鐘

概述

Package Relay 允許將多筆相關交易作為一個「包」(package) 一起提交和驗證, 而不是單獨處理每筆交易。這解決了 CPFP(子付父)場景中的手續費問題, 對閃電網路等二層協議的安全性至關重要。

開發狀態: Package Relay 正在積極開發中。部分功能已在 Bitcoin Core 中實現, 完整的 P2P 協議仍在進行中。

問題背景

CPFP 的困境

當前交易接受邏輯的問題:

場景: 父交易手續費太低
┌─────────────────────────────────────────────┐
│ Parent TX                                   │
│ - Fee: 1 sat/vB (低於 mempool 最低)         │
│ - 狀態: 被拒絕 ✗                            │
└─────────────────────────────────────────────┘
           │
           ▼ (花費 parent 的輸出)
┌─────────────────────────────────────────────┐
│ Child TX                                    │
│ - Fee: 100 sat/vB (很高)                    │
│ - 狀態: 無法驗證 (parent 不在 mempool)      │
└─────────────────────────────────────────────┘

問題:
1. Parent 手續費太低,被節點拒絕
2. Child 引用了不存在的輸入
3. 即使 child 願意付高費用,也無法幫助 parent
4. 形成死鎖

這對閃電網路很危險:
- 強制關閉交易可能手續費不足
- 無法及時確認可能導致資金損失

閃電網路場景

閃電網路承諾交易:

問題場景:
1. Alice 和 Bob 有通道
2. Bob 廣播過時的承諾交易
3. Alice 需要廣播懲罰交易
4. 但承諾交易手續費太低

傳統解決方案 - Anchor Outputs:
- 在承諾交易中添加小額錨定輸出
- 允許任一方使用 CPFP 加速

問題仍存在:
- 承諾交易必須先進入 mempool
- 如果完全被拒絕,錨定輸出無法使用

Package Relay 解決方案:
- 將承諾交易和 CPFP 交易一起提交
- 以包的總手續費率評估
- 確保時間敏感的交易能被處理

設計原理

Package 定義

什麼是 Package:

定義: 有依賴關係的交易集合

規則:
1. 拓撲排序 (父交易在前)
2. 連通 (形成 DAG)
3. 大小限制 (通常 <= 25 交易)
4. 總大小限制

Package 類型:
┌─────────────────────────────────────────────┐
│ 1. Child-With-Unconfirmed-Parents (CWUP)   │
│                                             │
│    Parent1    Parent2                       │
│       \         /                           │
│        \       /                            │
│         Child                               │
│                                             │
│ 最常見的場景                                │
└─────────────────────────────────────────────┘

┌─────────────────────────────────────────────┐
│ 2. Package CPFP                             │
│                                             │
│    Low-fee Parent                           │
│         │                                   │
│         ▼                                   │
│    High-fee Child                           │
│                                             │
│ 子交易為父交易提供手續費                    │
└─────────────────────────────────────────────┘

費率計算

Package 費率計算:

單交易費率:
fee_rate = fee / vsize

Package 費率:
package_fee_rate = total_fees / total_vsize

範例:
Parent: 200 vB, 200 sat (1 sat/vB)
Child:  100 vB, 5000 sat (50 sat/vB)
Package: 300 vB, 5200 sat (17.3 sat/vB)

驗收規則:
1. Package 費率 >= mempool 最低費率
2. 不低於增量費率 (如果替換)
3. 符合 mempool 限制

與單獨評估對比:
單獨: Parent 被拒 (1 sat/vB 太低)
Package: 接受 (17.3 sat/vB 足夠)

實作細節

驗證流程

interface Package {
  transactions: Transaction[];
  // 必須拓撲排序
}

interface PackageValidation {
  valid: boolean;
  accepted: Transaction[];
  rejected: Array<{ tx: Transaction; reason: string }>;
}

async function validatePackage(
  pkg: Package,
  mempool: Mempool
): Promise<PackageValidation> {
  const accepted: Transaction[] = [];
  const rejected: Array<{ tx: Transaction; reason: string }> = [];

  // 創建臨時 UTXO 視圖
  const tempUtxoSet = mempool.getUtxoSnapshot();

  // 計算 package 總費用和大小
  let totalFees = 0n;
  let totalVsize = 0;

  for (const tx of pkg.transactions) {
    // 驗證交易結構
    const structureResult = validateTransactionStructure(tx);
    if (!structureResult.valid) {
      rejected.push({ tx, reason: structureResult.error! });
      continue;
    }

    // 驗證輸入 (可以來自 mempool、區塊鏈或 package 內)
    const inputsResult = await validateInputs(tx, tempUtxoSet);
    if (!inputsResult.valid) {
      rejected.push({ tx, reason: inputsResult.error! });
      continue;
    }

    // 計算費用
    const fee = inputsResult.totalInput - getTotalOutput(tx);
    totalFees += fee;
    totalVsize += getVirtualSize(tx);

    // 更新臨時 UTXO 集合
    for (const input of tx.inputs) {
      tempUtxoSet.spend(input.txid, input.vout);
    }
    for (let i = 0; i < tx.outputs.length; i++) {
      tempUtxoSet.add(tx.txid, i, tx.outputs[i]);
    }

    accepted.push(tx);
  }

  // 檢查 package 費率
  const packageFeeRate = Number(totalFees) / totalVsize;
  if (packageFeeRate < mempool.getMinFeeRate()) {
    return {
      valid: false,
      accepted: [],
      rejected: accepted.map(tx => ({
        tx,
        reason: 'Package fee rate too low'
      }))
    };
  }

  // 提交到 mempool
  for (const tx of accepted) {
    mempool.add(tx);
  }

  return { valid: true, accepted, rejected };
}

Package RBF

// Package 替換 (Package RBF)
interface PackageRBFResult {
  success: boolean;
  replaced: Transaction[];
  error?: string;
}

async function packageRBF(
  pkg: Package,
  mempool: Mempool
): Promise<PackageRBFResult> {
  // 找出被替換的交易
  const toReplace: Transaction[] = [];

  for (const tx of pkg.transactions) {
    for (const input of tx.inputs) {
      const conflicting = mempool.findConflicting(input.txid, input.vout);
      if (conflicting) {
        toReplace.push(conflicting);
        // 也需要移除其所有後代
        toReplace.push(...mempool.getDescendants(conflicting.txid));
      }
    }
  }

  if (toReplace.length === 0) {
    // 不是 RBF,普通添加
    return { success: true, replaced: [] };
  }

  // 計算被替換交易的總費用
  const replacedFees = toReplace.reduce(
    (sum, tx) => sum + mempool.getFee(tx.txid),
    0n
  );

  // 計算新 package 的總費用
  let packageFees = 0n;
  for (const tx of pkg.transactions) {
    packageFees += calculateFee(tx);
  }

  // RBF 規則: 新費用必須足夠高
  const incrementalFee = mempool.getIncrementalRelayFee() *
    BigInt(getTotalVsize(pkg.transactions));

  if (packageFees < replacedFees + incrementalFee) {
    return {
      success: false,
      replaced: [],
      error: 'Insufficient fee for replacement'
    };
  }

  // 執行替換
  for (const tx of toReplace) {
    mempool.remove(tx.txid);
  }

  for (const tx of pkg.transactions) {
    mempool.add(tx);
  }

  return { success: true, replaced: toReplace };
}

P2P 協議

新訊息類型

Package Relay P2P 訊息 (提案中):

1. sendpackages
   - 在連接時協商
   - 表示支持 package relay

2. getpkgtxns
   - 請求 package 中的交易
   - 包含祖先 TXIDs

3. pkgtxns
   - 回覆 package 交易
   - 拓撲排序的交易列表

4. pkginfo
   - package 元數據
   - 包含費用、大小等信息

訊息流程:
┌────────────────────────────────────────────────┐
│ Node A                         Node B          │
│    │                              │            │
│    │ ─── sendpackages ──▶         │            │
│    │ ◀── sendpackages ───         │            │
│    │                              │            │
│    │ (收到孤立交易 child)          │            │
│    │                              │            │
│    │ ── getpkgtxns(child) ──▶     │            │
│    │                              │            │
│    │ ◀── pkgtxns(parent, child)── │            │
│    │                              │            │
│    │ (驗證並接受 package)          │            │
└────────────────────────────────────────────────┘

孤立交易處理

孤立交易與 Package Relay:

當前行為:
1. 收到交易,輸入不存在
2. 存入 orphan pool
3. 等待父交易到達
4. 如果父交易手續費低,可能永遠不到

Package Relay 改進:
1. 收到交易,輸入不存在
2. 請求缺失的祖先 (作為 package)
3. 收到完整 package
4. 以 package 費率評估
5. 接受或拒絕整個 package

優勢:
- 不依賴單獨交易的費率
- 更快解決孤立狀態
- 支持 CPFP 場景

應用場景

閃電網路

閃電網路安全改進:

1. 承諾交易 + CPFP
   ┌─────────────────────────────────────────┐
   │ Commitment TX (低費率)                  │
   │ - 雙方預簽名                           │
   │ - 費率可能過時                          │
   └─────────────────────────────────────────┘
              │
              ▼
   ┌─────────────────────────────────────────┐
   │ Anchor Spend TX (高費率)                │
   │ - 廣播時創建                           │
   │ - 補足手續費                           │
   └─────────────────────────────────────────┘

   Package Relay 確保:
   - 即使承諾 TX 費率過低
   - 可以與 anchor TX 一起接受

2. HTLC 超時
   - 必須在時間鎖前確認
   - Package Relay 提供保障

3. 懲罰交易
   - 需要及時確認以防資金損失
   - 可以用 CPFP 加速

錢包應用

錢包使用場景:

1. 加速卡住的交易
   - 原交易費率不足
   - 創建花費它的高費 CPFP 交易
   - 作為 package 提交

2. 批量支付
   - 多筆相關交易一起提交
   - 更可預測的確認行為

3. 依賴交易
   - 需要特定順序的交易
   - Package 確保一起處理

API 設計 (概念):
submitpackage([
  { tx: parentTxHex, fee: 200 },
  { tx: childTxHex, fee: 5000 }
])
// 返回: 整體接受或拒絕

Mempool 策略

限制與規則

Package 接受規則:

1. 大小限制
   - 最多 25 筆交易
   - 總大小 <= 101 KB
   - 與祖先/後代限制一致

2. 費率要求
   - Package 費率 >= mempool 最低
   - 個別交易可以低於最低
   - RBF 需要額外增量

3. 拓撲要求
   - 必須拓撲排序
   - 所有依賴在 package 內或已確認
   - 不能有循環

4. 標準性
   - 所有交易必須是標準交易
   - 腳本必須符合標準規則

失敗處理:
- 單個交易失敗: 整個 package 失敗
- 驗證按順序進行
- 返回首個失敗原因

實作狀態

Bitcoin Core 實作進度:

已完成:
- submitpackage RPC (v24.0)
- Package 驗證邏輯
- Package CPFP 評估

進行中:
- P2P package relay
- 動態 package 構建
- 優化孤立處理

RPC 使用:
$ bitcoin-cli submitpackage '["parent_hex", "child_hex"]'
{
  "package_msg": "success",
  "tx-results": {
    "txid1": { "vsize": 200, "fees": {...} },
    "txid2": { "vsize": 100, "fees": {...} }
  }
}

限制:
- 目前只支持 1 parent + 1 child
- 未來將擴展

未來發展

Package Relay 路線圖:

近期:
1. 完善 CPFP package 支持
2. P2P 協議標準化
3. 更多交易類型支持

中期:
1. 多 parent 支持
2. Package RBF 完善
3. 與 Erlay 整合

長期:
1. 動態 package 大小
2. 更複雜的 DAG 結構
3. 跨節點 package 協調

相關提案:
- v3 交易 (限制性 package 策略)
- Ephemeral anchors
- Cluster mempool

相關資源

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