首页
/ Bitcoin Core 内存池 RBF 替换政策:四条规则、feerate diagram 检查与源码实现

Bitcoin Core 内存池 RBF 替换政策:四条规则、feerate diagram 检查与源码实现

2026-09-06 10:57:28作者:温玫谨Lighthearted

Bitcoin Core 允许交易被更高费用的"替换交易"(replacement transaction)取代,这就是 Replace-by-Fee(RBF)政策。本文以仓库文档 doc/policy/mempool-replacements.md 为主体,完整讲解当前生效的四条 RBF 规则及其设计理由,并逐条对应到 src/policy/rbf.cppsrc/validation.cpp 中的源码实现与 -incrementalrelayfee 等配置参数,读完后可理解一笔 RBF 替换在内存池准入流程中究竟经过了哪些检查、每个参数如何影响替换行为。

核心概念:什么交易可以替换什么交易

文档首先给出两组关键定义:

  • 直接冲突交易(directly conflicting transaction):一笔交易与内存池中的交易"直接冲突",当且仅当它们花费了相同的一个或多个输入(input)。一笔交易可能与多笔内存池交易同时冲突。
  • 原始交易(original transactions):替换交易可以直接冲突的那些交易,以及它们在内存池中的后代(descendants)——因为后代依赖被替换交易的输出,原交易出局后它们也无法确认,必须一并驱逐。

也就是说,一次 RBF 替换驱逐的往往不是一笔交易,而是一棵"冲突交易 + 全部内存池后代"的集合。这个定义直接决定了后面规则 #5(按集群计数)和规则 #3/#4(费用必须覆盖整棵被驱逐子树)的计算口径。

文档将"已移除"的规则 #1 和 #2 明确标注为 (Removed),当前政策由规则 #3~#6 构成。结合文档 History 一节可以推断:RBF 信号(signaling)自 PR 30592 起不再是替换的前提条件,即原先要求"原交易必须按 BIP125 序列号规则表明可被替换"的规则已被移除;而规则 #5 按"集群(cluster)"而非交易笔数设限,也表明旧版按"替换交易数量"设限的规则已被集群化内存池(cluster mempool)的新模型取代。

规则 #3:替换交易的绝对费用不得低于被替换交易之和

文档规定:

The replacement transaction pays an absolute fee of at least the sum paid by the original transactions.

即替换交易支付的绝对费用(total fee)必须 ≥ 所有原始交易支付费用之和。

文档给出的 Rationale(设计理由)有两层:

  1. 如果只要求替换交易的费率(feerate)更高,攻击者可以绕过节点最低中继费率要求,让网络反复中继"稍微小一点、费率略高"的替换交易,而不增加任何总费用——中继带宽被白白消耗;
  2. 若某笔原始交易本就会被一个"经济上理性的矿工"打进下一个块,那么允许替换交易降低下一块的绝对费用就是与矿工激励不相容(incentive-incompatible)的——矿工没有动力接受这笔替换。

对应源码在 src/policy/rbf.cppPaysForRBF(),注释中直接写明这是 "Rule #3":

// Rule #3: The replacement fees must be greater than or equal to fees of the
// transactions it replaces, otherwise the bandwidth used by those conflicting
// transactions would not be paid for.
if (replacement_fees < original_fees) {
    return strprintf("rejecting replacement %s, less fees than conflicting txs; %s < %s", ...);
}

传入的 original_fees全部 all_conflicts(冲突交易加后代)的修改后费用之和,这正是文档"original transactions"定义的代码化。

规则 #4:增量费用必须按增量中继费率"购买"自己的带宽

文档规定:替换交易付出的额外费用(替换交易绝对费用 − 原始交易费用之和),必须至少按节点增量中继费率(incremental relay feerate)为替换交易的全部带宽付费。文档给出的算例:

若增量中继费率为 0.1 satoshi/vB,替换交易总大小为 500 虚拟字节,则替换交易的费用必须比原始交易之和至少高出 50 satoshi(0.1 × 500)。

Rationale:防止一类 DoS 攻击——攻击者让网络反复中继"每次只多加 1 satoshi"的替换交易。

同样的 PaysForRBF() 函数实现了 "Rule #4"(见 src/policy/rbf.cpp 第 117~123 行):

// Rule #4: The new transaction must pay for its own bandwidth. Otherwise, we have a DoS
// vector where attackers can cause a transaction to be replaced (and relayed) repeatedly
// by increasing the fee by tiny amounts.
CAmount additional_fees = replacement_fees - original_fees;
if (additional_fees < relay_fee.GetFee(replacement_vsize)) { ... }

注意 relay_fee.GetFee(replacement_vsize) 是按整笔替换交易的 vsize 计费,而不是只按"新增部分"计费——这与文档"pays for the replacement transaction's bandwidth"的表述一致,也意味着替换交易越大,规则 #4 要求的增量费用越高。

增量中继费率从哪里来:-incrementalrelayfee

文档 History 一节明确指出:用于计算规则 #4 所需增量费用的增量中继费率独立于 -minrelaytxfee,可通过 -incrementalrelayfee 单独配置(PR #9380);其默认值为 0.1 sat/vB(PR #33106)。

参数解析位于 src/node/mempool_args.cpp

// incremental relay fee sets the minimum feerate increase necessary for replacement
// in the mempool and the amount the mempool min fee increases above the feerate of
// txs evicted due to mempool limiting.
if (const auto arg{argsman.GetArg("-incrementalrelayfee")}) {
    if (std::optional<CAmount> inc_relay_fee = ParseMoney(*arg)) {
        mempool_opts.incremental_relay_feerate = CFeeRate{inc_relay_fee.value()};
    } else {
        return util::Error{AmountErrMsg("incrementalrelayfee", *arg)};
    }
}

有两点值得注意:

  1. 从源码注释看,该参数除了决定 RBF 替换的门槛,还兼作内存池满时逐出交易后 mempool 最低费率的抬升幅度,一参两用;
  2. 代码中有一个联动约束(src/node/mempool_args.cpp):存在 static_assert(DEFAULT_MIN_RELAY_TX_FEE == DEFAULT_INCREMENTAL_RELAY_FEE),且若用户只设置了 -incrementalrelayfee 且其值高于 -minrelaytxfee,节点会自动把 minrelaytxfee 抬升到相同值并记录日志 "Increasing minrelaytxfee to %s to match incrementalrelayfee"。

因此在 bitcoin.conf 中的典型配置为:

# 参考 share/examples/bitcoin.conf 的格式
incrementalrelayfee=0.00001   # 0.1 sat/vB,即 0.00001 BTC/kB 当量费率

该值最终以 m_pool.m_opts.incremental_relay_feerate 的形式传入准入检查(见 src/validation.cpp)。

规则 #5:受影响的冲突集群数量不得超过 100

文档规定:与冲突交易对应的不同集群(distinct clusters)数量不得超过 100Rationale:限制一次性从内存池移除这么多交易所需的 CPU 开销。

上限常量定义在 src/policy/rbf.h

/** Maximum number of unique clusters that can be affected by an RBF (Rule #5);
 * see GetEntriesForConflicts() */
inline constexpr uint32_t MAX_REPLACEMENT_CANDIDATES{100};

实现在 src/policy/rbf.cppGetEntriesForConflicts():先用 pool.GetUniqueClusterCount(iters_conflicting) 统计冲突交易所属的不同集群数,超过 100 立即拒绝;否则才遍历每笔冲突交易调用 pool.CalculateDescendants() 展开完整驱逐集合。源码注释特别指出:集群数上限保证了对该函数的单次调用工作量有界,"实际重新线性化的集群数可能因集群分裂(cluster splitting)而略多于该数值,但上限仍保证计算量可控"——这是集群化内存池数据结构的特有语义。

规则 #6:feerate diagram 必须被严格改进

这是当前政策中最"新"的一条(文档 History 记载:feerate diagram 政策随 v31.0 切换至集群内存池时一同启用)。文档规定:

The feerate diagram of the mempool must be strictly improved by the replacement transaction.

Rationale:保证替换之后,未来所有区块(忽略区块尾部效应)中的区块费用都会上升。"feerate diagram"(费率图)是从源码结构看对"内存池按累计费率排序后各深度的费用曲线"的抽象:把内存池交易按费率分层,替换必须使整条曲线严格占优,而不只是让被替换的那几笔交易看起来更划算。

实现在 src/policy/rbf.cpp

std::optional<std::pair<DiagramCheckError, std::string>> ImprovesFeerateDiagram(CTxMemPool::ChangeSet& changeset)
{
    // Require that the replacement strictly improves the mempool's feerate diagram.
    const auto chunk_results{changeset.CalculateChunksForRBF()};

    if (!chunk_results.has_value()) {
        return std::make_pair(DiagramCheckError::UNCALCULABLE, util::ErrorString(chunk_results).original);
    }

    if (!std::is_gt(CompareChunks(chunk_results.value().second, chunk_results.value().first))) {
        return std::make_pair(DiagramCheckError::FAILURE, "insufficient feerate: does not improve feerate diagram");
    }
    return std::nullopt;
}

检查基于 CTxMemPool::ChangeSet(内存池"拟变更集合")对替换前后两个 chunk 序列做比较,用 std::is_gt 要求严格大于,对应文档的 "strictly improved"。失败时区分两种错误类型(见 src/policy/rbf.h):UNCALCULABLE(因拓扑等原因无法计算)与 FAILURE(新图未严格占优),这对上层判断"是否值得换一笔更大的替换交易"具有语义区别。

准入调用链:四条规则在 MemPoolAccept::ReplacementChecks 中的执行顺序

各规则不是孤立的,它们按固定顺序串接在内存池准入流程中。核心调用链位于 src/validation.cppMemPoolAccept::ReplacementChecks(Workspace& ws)

  1. 规则 #5GetEntriesForConflicts(tx, m_pool, ws.m_iters_conflicting, all_conflicts) 计算全部待驱逐交易并执行集群数上限检查,失败则返回 TX_MEMPOOL_POLICY(错误消息 "too many potential replacements");
  2. 累加 all_conflicts 中每笔交易的 GetModifiedFee()GetTxSize(),得到原始交易的总费用/总大小;
  3. 规则 #3 + #4PaysForRBF(m_subpackage.m_conflicting_fees, ws.m_modified_fees, ws.m_vsize, m_pool.m_opts.incremental_relay_feerate, hash),失败返回 TX_RECONSIDERABLE("insufficient fee")——注意结果是 reconsiderable 而非 permanent,因为同一交易在包(package)上下文中可能重新判定;
  4. 将待移除交易 StageRemoval 进 changeset,并执行 CheckMemPoolPolicyLimits()(集群尺寸限制,失败报 "too-large-cluster");
  5. 规则 #6ImprovesFeerateDiagram(*m_subpackage.m_changeset),由于上一步已确认集群限制满足,此处失败只能来自费用不足,失败返回 "replacement-failed"。

包 RBF(Package RBF)的额外约束

同一文件稍后的 MemPoolAccept::PackageRBFChecks()src/validation.cpp)处理"一笔替换交易 + 一笔依赖它的后代"同时提交的情形。从源码看有两条硬性限制:

  • 替换提议必须恰好是 1 父 1 子(1-parent-1-child)两笔交易,否则报 "package RBF failed: package must be 1-parent-1-child";
  • 若包中的交易已有内存池中的父交易,则不考虑包 RBF,因为那会使受影响的集群规模超过 2(注释同时说明了放宽该限制需要重新审视 CCoinsViewMemPool::PackageAddTransactionAcceptMultipleTransactions 中对可用输入跟踪方式)。

RBF 信号状态:IsRBFOptIn 的现状

文档 History 记载:自 PR 30592 起,替换交易不再需要 RBF 信号。但从源码结构看,BIP125 信号检查逻辑仍然保留在 src/policy/rbf.cpp

RBFTransactionState IsRBFOptIn(const CTransaction& tx, const CTxMemPool& pool)
{
    // First check the transaction itself.
    if (SignalsOptInRBF(tx)) {
        return RBFTransactionState::REPLACEABLE_BIP125;
    }
    // If this transaction is not in our mempool, then we can't be sure
    // we will know about all its inputs.
    if (!pool.exists(tx.GetHash())) {
        return RBFTransactionState::UNKNOWN;
    }
    // If all the inputs have nSequence >= maxint-1, it still might be
    // signaled for RBF if any unconfirmed parents have signaled.
    ...
}

其语义是:先检查交易自身(输入序列号 < maxint-1 即按 BIP125 视为信号 RBF),再沿内存池祖先链检查是否有未确认父交易信号了 RBF,返回三态 RBFTransactionStateUNKNOWN / REPLACEABLE_BIP125 / FINAL,定义于 src/policy/rbf.h)。可以推断,该函数在当前版本中主要服务于对外查询与钱包侧的状态报告,而不再作为 ReplacementChecks 的准入门槛——这也与文档"signaling ... is no longer required"的表述一致。

政策演进历史(文档 History 全量继承)

文档末尾的 History 一节记录了 RBF 政策的完整演进,按时间顺序整理如下(版本与 PR 编号均以文档原文为准):

里程碑 版本 说明
Opt-in 完整 RBF 在内存池与打包中生效 v0.12.0 PR 6871,不要求继承信号
BIP125 定义 基于 Bitcoin Core 的实现反推制定
-incrementalrelayfee 成为独立可配置参数 PR #9380,与 -minrelaytxfee 分离
钱包 GUI 默认启用 RBF v0.18.1 PR #11605
完整 RBF 成为可配置的内存池政策 v24.0 PR #25353
完整 RBF 成为默认政策 v28.0 PR #30493
RBF 信号不再是必需条件 PR 30592
增量中继费率默认值定为 0.1 sat/vB PR #33106
feerate diagram 政策启用 v31.0 随切换至集群内存池一同生效(即规则 #6)

这条演进线清晰地展示了 RBF 从"必须显式 opt-in 的受限功能"走向"默认全量开放 + 用费用与图论约束替代信号位"的过程:早期的信号要求(旧规则 #1)和按交易笔数设限(旧规则 #2)被移除,取而代之的是费用下限、带宽付费、集群规模上限与 feerate diagram 严格改进这四条经济性与资源性约束。

验证入口:单元测试与 fuzz 测试

仓库为这套规则提供了对应的测试资产,可作为行为核对的入口:

  • src/test/rbf_tests.cpp:针对上述各检查函数的单元测试,直接覆盖 PaysForRBFGetEntriesForConflicts 等函数的边界情形;
  • src/test/fuzz/rbf.cpp:以模糊测试方式驱动替换检查路径,验证各拒绝分支的鲁棒性。

ImprovesFeerateDiagram 返回 UNCALCULABLEFAILURE 的区分、PackageRBFChecks 对 1-父-1-子结构的断言等行为,均可在以上测试中找到对应断言。

小结与延伸阅读

当前 Bitcoin Core 内存池中的 RBF 替换政策可以概括为一句话:一笔(或 1-父-1-子的两笔)替换交易,在通过全部其他共识与政策检查的前提下,还需满足"绝对费用不降(#3)、增量费用按 -incrementalrelayfee 买断全部带宽(#4)、受影响集群不超过 100(#5)、feerate diagram 严格改进(#6)"四重条件

延伸阅读可结合同目录文档:mempool-design.md 讲解内存池整体结构与集群模型,mempool-terminology.md 给出集群、子包等术语定义,packages.md 说明包中继政策如何与包 RBF 协同;实现侧入口为 src/policy/rbf.hsrc/policy/rbf.cppsrc/validation.cpp,参数侧为 src/node/mempool_args.cpp

适用前提说明:本文所有规则、默认值与调用链均基于当前仓库(Bitcoin Core integration/staging tree)的实际内容;若以特定发布版为准,请先对照 doc/release-notes/ 下相应版本说明确认 feerate diagram 政策(v31.0 起)与信号豁免(PR 30592)是否已包含在内。

登录后查看全文
热门项目推荐
相关项目推荐