首页
/ Bitcoin Core 25.2 发布要点解析:getrawtransaction 崩溃、钱包密钥池与区块突变校验等关键修复

Bitcoin Core 25.2 发布要点解析:getrawtransaction 崩溃、钱包密钥池与区块突变校验等关键修复

2026-09-06 18:57:38作者:宣利权Counsellor

导读

Bitcoin Core 25.2 是一个面向 25.x 稳定分支的维护型(bugfix)发布版本,核心目标是修复 25.1 及更早版本中暴露的一批内存安全与稳定性问题,涵盖 GUI、RPC、钱包与 P2P 网络四个模块。本文将以此版本发布说明为主体,逐条展开升级与兼容性要求、各项修复的技术背景与影响面,并结合当前仓库源码取证其落地形态,帮助你判断是否需要升级、升级时需要注意什么,以及每个修复在代码层的真实位置。

版本定位:一次典型的补丁式发布

发布说明开篇即明确 25.2 的定位:本版本包含各种错误修复(bug fixes)、性能改进(performance improvements)以及更新的翻译,并未引入新的共识规则或重大功能。这与 Bitcoin Core 的版本节奏一致——主版本(如 25.0)发布功能,而 x.1/x.2 序列专注于稳定性收尾。

该发布说明在仓库中的归档位置为 doc/release-notes/release-notes-25.2.md,属于 release-notes 系列文档的一部分(同目录下还保留了 25.0、25.1 以及 26.x、27.x 直至 31.x 的各版本说明,可横向对比版本演进)。需要特别说明的是,当前仓库的 顶层 CMakeLists.txtCLIENT_VERSION_MAJOR/MINOR/BUILD 显示版本为 31.99.0 且 CLIENT_VERSION_IS_RELEASE"false",即本仓库主干的开发进度已远超前于 25.2。因此下文涉及的修复在主干中均已合入多年,只是随着重构演化为不同的代码形态——我们恰好可以借此观察「修复当时是什么样、今天变成了什么样」。

如何升级到 25.2

发布说明给出的升级流程非常标准,适用于从任意旧版本直升:

  1. 关闭节点:如果正在运行旧版本,先将其关闭;
  2. 等待完全退出:务必等待进程完全退出(部分情况下可能需要几分钟,如钱包/数据库迁移或正常 flush);
  3. 替换二进制文件
    • Windows:运行安装程序(installer);
    • macOS:覆盖拷贝 /Applications/Bitcoin-Qt
    • Linux:替换 bitcoind / bitcoin-qt 可执行文件。

两点关键提示:

  • 可直接跨 EOL 版本升级:文档明确说明,从已经达到生命周期终点(EOL)的版本直接升级是可行的,但如果数据目录需要迁移,可能耗时较长。
  • 旧钱包格式兼容:旧版本的比特币钱包(legacy wallet 数据格式)在一般情况下仍然受支持,即不会因升级而强制丢失钱包数据。

值得提醒的是:发布说明本身的升级对象是「运行 25.2 之前任意版本」的用户;如果你当前运行的是 25.x 系列,25.2 是建议的收尾版本;而本仓库主干(31.99 开发版)对应的最终发布,则应在相应正式版本发布后再参考其发布说明进行升级。

支持平台与兼容性声明

25.2 发布说明中保留了与主版本一致的平台支持声明:

  • Linux 内核系列系统:完全支持并经过大量测试;
  • macOS 10.15+:完全支持;
  • Windows 7 及更新版本:完全支持;
  • 其他类 Unix 系统:理论上可用,但测试频率较低;
  • 明确建议:不要在不受支持的平台上运行 Bitcoin Core。

这一声明是 Bitcoin Core 一贯的「支持范围保守化」策略,把有限的 CI 资源集中在主流平台,同时不禁止用户在更多系统上自行编译使用。

Notable changes:四条模块的六项修复逐条解析

25.2 的修复集中在 GUI、RPC、Wallet、P2P/网络四个模块。下面逐条给出技术解读与源码取证。

GUI:修复交易视图勾选 "Mask values" 时崩溃

  • 修复条目gui#774 Fix crash on selecting "Mask values" in transaction view

这是来自 Bitcoin Core GUI 独立仓库(bitcoin-core/gui)的修复,合入到 25.2 的 Qt 钱包界面中。崩溃场景是用户在交易视图中启用/选中「Mask values」(隐藏交易金额显示)选项时触发。从修复类型看属于典型的 UI 状态切换时的空指针/无效引用问题:金额遮蔽状态切换会触发交易记录列表的局部重绘或取数逻辑,若某条交易记录的展示字段尚未完整填充,就可能在刷新路径上发生崩溃。该修复并未改变任何链上逻辑或数据结构,纯属界面健壮性改进,因此本小节在源码层面只需说明:Qt 界面相关的交易展示逻辑位于 src/qt/ 目录(373 个文件,含 .ui.cpp.ts 翻译文件),金额遮蔽等展示功能不涉及 src/wallet/ 的钱包核心状态,属于纯展示层缺陷。

RPC:修复 getrawtransaction 段错误

  • 修复条目#29003 rpc: fix getrawtransaction segfault

getrawtransaction 是 Bitcoin Core 最常用的底层 RPC 之一,其调用链在主干中位于 src/rpc/rawtransaction.cpp。该修复解决的是特定查询路径下触发空指针解引用(segfault)的问题——即节点进程直接崩溃,而不是返回错误码。虽然发布说明未展开崩溃的精确复现条件,但从当前主干中该 RPC 的实现形态可以清晰看到为杜绝此类崩溃而设计的防御结构,这正是该修复的「最终形态」:

  • 调用 GetTransaction(...) 拿到交易指针后,先判断是否为空,空则构造并抛出明确的 JSON-RPC 错误(rawtransaction.cpp),而不是继续解引用;
  • 错误消息按场景区分:
    • 指定了 blockhash 但区块无数据 → RPC_MISC_ERROR "Block not available"
    • 区块内有该区块但找不到交易 → "No such transaction found in the provided block"
    • 未开 -txindex 且不在 mempool → 提示 "Use -txindex or provide a block hash to enable blockchain transaction queries"
    • -txindex 正在同步 → 提示 "Blockchain transactions are still in the process of being indexed"
  • 在 verbosity ≥ 1 的明细返回路径上,若未显式提供 blockhash,则依据返回的 hash_block 反查区块索引,并显式允许该索引为空(对应 mempool 交易),注释原文为 “May be nullptr for mempool transactions”(rawtransaction.cpp),后续代码必须对空区块索引做分支处理——这正是当年段错误风险点的典型位置。

此外还有一处特殊防护:当请求的 txid 等于创世区块 coinbase 交易的 hashMerkleRoot 时,直接抛出 "The genesis block coinbase is not considered an ordinary transaction and cannot be retrieved"rawtransaction.cpp),避免对不存在于任何区块交易集合的特殊交易做无效索引。

该 RPC 的典型用法(取自当前源码中的 RPCExamplesrawtransaction.cpp):

bitcoin-cli getrawtransaction "mytxid"
bitcoin-cli getrawtransaction "mytxid" 1
bitcoin-cli getrawtransaction "mytxid" 0 "myblockhash"
bitcoin-cli getrawtransaction "mytxid" 2 "myblockhash"
  • 默认(verbosity=0)只返回十六进制原始交易;
  • verbosity=1 返回带解码字段的 JSON;
  • verbosity=2 额外返回 prevout 等上下文;
  • 第三个参数 blockhash 用于绕过 -txindex、直接从指定区块取交易。

Wallet #29176:修复 WalletBatch::EraseRecords 中的 use-after-free

  • 修复条目#29176 wallet: Fix use-after-free in WalletBatch::EraseRecords

这是一个**内存安全(use-after-free,悬垂引用)**修复,属于最高优先级的正确性问题。WalletBatch 是钱包数据库(Berkeley DB 风格的批次访问层)的封装,EraseRecords 用于按记录类型整体删除某一类钱包记录。

从当前主干实现看,该函数已经演化为「按 key 类型前缀整体批量擦除」的形态:

bool WalletBatch::EraseRecords(const std::unordered_set<std::string>& types)
{
    return std::all_of(types.begin(), types.end(), & {
        return m_batch->ErasePrefix(DataStream() << type);
    });
}

walletdb.cpp

use-after-free 的根源在于:早期实现中该函数基于数据库游标逐条迭代删除记录;在删除进行的同时若迭代器所依赖的内部缓冲区被释放/重建,就会读取到已释放内存,造成崩溃或潜在的不确定行为。修复方向是放弃「迭代中逐条删除」的脆弱模式,改为先构造完整的前缀 key,再对整段前缀一次性擦除,从根上消除了游标失效问题。这正是为什么今天我们看到的是 ErasePrefix 一次调用完成全部删除的形态。

在主干中该方法的调用点也值得一读:旧式(legacy)钱包删除自身全部记录时,通过 scriptpubkeyman.cppLegacyDataSPKM::DeleteRecordsWithDB 传入 DBKeys::LEGACY_TYPES 调用 batch.EraseRecords(...)。也就是说,这段代码的每次执行都发生在「销毁/重建 legacy 钱包脚本公钥管理器」的关键路径上,一旦出错会直接影响钱包迁移与重置流程。

Wallet #29510:getrawchangeaddress / getnewaddress 失败不应损耗 descriptor 钱包密钥池

  • 修复条目#29510 wallet: getrawchangeaddress and getnewaddress failures should not affect keypools for descriptor wallets

这条修复直击一个「看似错误、实为资产安全」的场景:在 descriptor 钱包(默认新建钱包类型)中调用 getnewaddress(新收款地址)或 getrawchangeaddress(新找零地址)时,如果操作中途失败,此前被预取出的密钥不应从密钥池(keypool)中永久扣除。

先看当前主干中两个 RPC 背后 CWallet 层的调用形态(wallet.cpp):

  • GetNewDestination(type, label):获取脚本公钥管理器(GetScriptPubKeyMan)后调用 spk_man->GetNewDestination(type),失败路径直接返回 util::Error,并且只有成功时才写入地址簿(SetAddressBook);
  • GetNewChangeDestination(type):基于 ReserveDestination 完成「预取 → 确认」两段式流程,只有 GetReservedDestination 成功后才调用 KeepDestination()

ReserveDestination 的设计是整个修复的语义核心(wallet.h):

  • 构造函数本身不占用任何地址,必须显式调用 GetReservedDestination() 才算「预取」;
  • 预取成功后调用 KeepDestination() 表示「地址已被真实使用,允许从密钥池移除」;
  • 若预取成功却没有调用 KeepDestination(),则对象析构时自动调用 ReturnDestination(),把密钥放回密钥池,头文件注释明确写道:“If an address is reserved and KeepDestination() is not called, then the address will be returned when the ReserveDestination goes out of scope.”

在修复前,descriptor 钱包的失败路径上存在密钥被标记为已用而未归还的缺陷,用户反复发起失败的地址生成请求时,密钥池会被静默消耗。修复后的语义是:失败即归还、成功才扣除,无论中途在哪个环节出错,密钥池数量都不应减少。这也解释了为何 getnewaddress/getrawchangeaddress 这类「看起来只读」的调用,在实现上要经过如此严格的资源管理抽象。

P2P:不再处理「被突变(mutated)」的区块

  • 修复条目
    • #29412 p2p: Don't process mutated blocks
    • #29524 p2p: Don't consider blocks mutated if they don't connect to known prev block

这是 25.2 中网络层最重要的一组共识安全修复。所谓「突变区块(mutated block)」指区块头/交易数据的哈希与网络中继的承诺不一致(典型如 witness 承诺与默克尔根不匹配)的恶意或异常区块。在 Bitcoin Core 的 BlockTransactions(紧凑区块/BIP152 区块中继)处理路径上,节点原先可能对这类区块继续做交易请求与处理尝试,造成不必要的网络带宽消耗与潜在的状态混乱。

从当前主干看,这一防御逻辑位于区块处理函数对完整区块的入口校验处(net_processing.cpp):

const CBlockIndex* prev_block{WITH_LOCK(m_chainman.GetMutex(),
    return m_chainman.m_blockman.LookupBlockIndex(pblock->hashPrevBlock))};

// Check for possible mutation if it connects to something we know so we can check for DEPLOYMENT_SEGWIT being active
if (prev_block && IsBlockMutated(/*block=*/*pblock,
                   /*check_witness_root=*/DeploymentActiveAfter(prev_block, m_chainman, Consensus::DEPLOYMENT_SEGWIT))) {
    LogDebug(BCLog::NET, "Received mutated block from peer=%d\n", peer.m_id);
    Misbehaving(peer, "mutated block");
    WITH_LOCK(cs_main, RemoveBlockRequest(pblock->GetHash(), peer.m_id));
    return;
}

这段代码逐行印证了 25.2 的两条修复,且与主干的后续演进一脉相承:

  1. #29412 的核心:发现区块「被突变」后不再继续处理,而是记录日志 "Received mutated block from peer=%d"、给对等节点施加惩罚(Misbehaving(peer, "mutated block"))、移除对该区块的请求(RemoveBlockRequest),随后直接返回。这与修复标题 "Don't process mutated blocks" 完全对应。
  2. #29524 的细化:突变校验被限定在「该区块能连接到已知前序区块」的前提下进行——即代码中的 if (prev_block && ...) 守卫,以及注释所强调的 “if it connects to something we know”。原因在于:IsBlockMutated 的 witness 根检查需要依据前序区块是否已激活 SegWit(DeploymentActiveAfter(prev_block, ...))来决定检查强度。如果一个区块的前序未知,我们就无法判断 SegWit 是否激活,也就不应该草率判定其为「突变」并惩罚对端,否则可能误伤正常节点。这正对应 #29524 的标题 "Don't consider blocks mutated if they don't connect to known prev block"。

这两条修复合在一起构成一套完整的「突变区块拒绝」策略:先确认可评估上下文,再判定突变,最后惩罚并丢弃

致谢与贡献者

发布说明末尾列出了 25.2 的直接代码贡献者,按惯例也向通过 Transifex 平台参与翻译的所有社区成员致谢。直接贡献者名单如下:

  • Martin Zumsande
  • Sebastian Falbesoner
  • MarcoFalke
  • UdjinM6
  • dergoegge
  • Greg Sanders

从名单可以看到 25.2 是一个「小而精」的维护版本:六名直接贡献者 + 翻译社区,工作量集中在上述几条高价值修复上,没有大规模新功能,符合 x.2 维护版的定位。

从当前仓库追溯这些修复的实践建议

由于本仓库主干(31.99.0 开发版)已远超 25.2,若你想精确研究「25.2 发布当时」的代码差异,建议结合 git 历史操作而非仅看主干文件:

  1. 该发布说明本身归档在 doc/release-notes/release-notes-25.2.md,可作为阅读入口;
  2. 在仓库中执行 git log --oneline --grep="29412\|29510\|29003\|29176" 可定位对应 PR 的合并提交;每条修复条目开头的 #NNNNN 即 Bitcoin Core 主仓库的 PR 编号,可在本地 git show <commit> 中查看当时的完整 diff 与测试;
  3. 主干上这些代码的「今日形态」则以上文给出的文件链接为准,例如 net_processing.cppwalletdb.cppwallet.hrawtransaction.cpp

小结:是否应当升级到 25.2

综合发布说明内容,可以给出如下判断:

  • 所有 25.x 用户都应升级到 25.2:两条钱包内存安全修复(#29176 use-after-free、#29510 密钥池损耗)与一条 RPC 段错误修复(#29003)都属于「长期运行节点应尽快吸收」的稳定性修复;P2P 的两条区块突变校验改进则直接关乎带宽与对等节点惩罚的公平性;
  • 升级成本极低:无共识变更、无新 RPC、无数据结构迁移声明,标准的「停节点 → 等退出 → 换二进制 → 启动」流程即可;
  • GUI 用户受益于 "Mask values" 崩溃修复,特别是经常在交易视图中切换金额显示的用户;
  • 若你运行在 25.2 之后的更高版本(如 26.x~31.x),这些修复早已包含在内,无需额外操作。

本文所有关于代码形态的描述均可在仓库对应路径中复核;关于修复动机的描述以 doc/release-notes/release-notes-25.2.md 的条目为准,未引入任何外部来源的推测性结论。

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