Bitcoin Core 内存池 RBF 替换政策:四条规则、feerate diagram 检查与源码实现
Bitcoin Core 允许交易被更高费用的"替换交易"(replacement transaction)取代,这就是 Replace-by-Fee(RBF)政策。本文以仓库文档 doc/policy/mempool-replacements.md 为主体,完整讲解当前生效的四条 RBF 规则及其设计理由,并逐条对应到 src/policy/rbf.cpp、src/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(设计理由)有两层:
- 如果只要求替换交易的费率(feerate)更高,攻击者可以绕过节点最低中继费率要求,让网络反复中继"稍微小一点、费率略高"的替换交易,而不增加任何总费用——中继带宽被白白消耗;
- 若某笔原始交易本就会被一个"经济上理性的矿工"打进下一个块,那么允许替换交易降低下一块的绝对费用就是与矿工激励不相容(incentive-incompatible)的——矿工没有动力接受这笔替换。
对应源码在 src/policy/rbf.cpp 的 PaysForRBF(),注释中直接写明这是 "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)};
}
}
有两点值得注意:
- 从源码注释看,该参数除了决定 RBF 替换的门槛,还兼作内存池满时逐出交易后 mempool 最低费率的抬升幅度,一参两用;
- 代码中有一个联动约束(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)数量不得超过 100。Rationale:限制一次性从内存池移除这么多交易所需的 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.cpp 的 GetEntriesForConflicts():先用 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.cpp 的 MemPoolAccept::ReplacementChecks(Workspace& ws):
- 规则 #5:
GetEntriesForConflicts(tx, m_pool, ws.m_iters_conflicting, all_conflicts)计算全部待驱逐交易并执行集群数上限检查,失败则返回TX_MEMPOOL_POLICY(错误消息 "too many potential replacements"); - 累加
all_conflicts中每笔交易的GetModifiedFee()与GetTxSize(),得到原始交易的总费用/总大小; - 规则 #3 + #4:
PaysForRBF(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)上下文中可能重新判定; - 将待移除交易
StageRemoval进 changeset,并执行CheckMemPoolPolicyLimits()(集群尺寸限制,失败报 "too-large-cluster"); - 规则 #6:
ImprovesFeerateDiagram(*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::PackageAddTransaction在AcceptMultipleTransactions中对可用输入跟踪方式)。
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,返回三态 RBFTransactionState(UNKNOWN / 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:针对上述各检查函数的单元测试,直接覆盖
PaysForRBF、GetEntriesForConflicts等函数的边界情形; - src/test/fuzz/rbf.cpp:以模糊测试方式驱动替换检查路径,验证各拒绝分支的鲁棒性。
ImprovesFeerateDiagram 返回 UNCALCULABLE 与 FAILURE 的区分、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.h、src/policy/rbf.cpp 与 src/validation.cpp,参数侧为 src/node/mempool_args.cpp。
适用前提说明:本文所有规则、默认值与调用链均基于当前仓库(Bitcoin Core integration/staging tree)的实际内容;若以特定发布版为准,请先对照 doc/release-notes/ 下相应版本说明确认 feerate diagram 政策(v31.0 起)与信号豁免(PR 30592)是否已包含在内。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00