Bitcoin Core 25.2 发布要点解析:getrawtransaction 崩溃、钱包密钥池与区块突变校验等关键修复
导读
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.txt 中 CLIENT_VERSION_MAJOR/MINOR/BUILD 显示版本为 31.99.0 且 CLIENT_VERSION_IS_RELEASE 为 "false",即本仓库主干的开发进度已远超前于 25.2。因此下文涉及的修复在主干中均已合入多年,只是随着重构演化为不同的代码形态——我们恰好可以借此观察「修复当时是什么样、今天变成了什么样」。
如何升级到 25.2
发布说明给出的升级流程非常标准,适用于从任意旧版本直升:
- 关闭节点:如果正在运行旧版本,先将其关闭;
- 等待完全退出:务必等待进程完全退出(部分情况下可能需要几分钟,如钱包/数据库迁移或正常 flush);
- 替换二进制文件:
- 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";
- 指定了 blockhash 但区块无数据 →
- 在 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 的典型用法(取自当前源码中的 RPCExamples,rawtransaction.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);
});
}
use-after-free 的根源在于:早期实现中该函数基于数据库游标逐条迭代删除记录;在删除进行的同时若迭代器所依赖的内部缓冲区被释放/重建,就会读取到已释放内存,造成崩溃或潜在的不确定行为。修复方向是放弃「迭代中逐条删除」的脆弱模式,改为先构造完整的前缀 key,再对整段前缀一次性擦除,从根上消除了游标失效问题。这正是为什么今天我们看到的是 ErasePrefix 一次调用完成全部删除的形态。
在主干中该方法的调用点也值得一读:旧式(legacy)钱包删除自身全部记录时,通过 scriptpubkeyman.cpp 的 LegacyDataSPKM::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 的两条修复,且与主干的后续演进一脉相承:
- #29412 的核心:发现区块「被突变」后不再继续处理,而是记录日志
"Received mutated block from peer=%d"、给对等节点施加惩罚(Misbehaving(peer, "mutated block"))、移除对该区块的请求(RemoveBlockRequest),随后直接返回。这与修复标题 "Don't process mutated blocks" 完全对应。 - #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 历史操作而非仅看主干文件:
- 该发布说明本身归档在 doc/release-notes/release-notes-25.2.md,可作为阅读入口;
- 在仓库中执行
git log --oneline --grep="29412\|29510\|29003\|29176"可定位对应 PR 的合并提交;每条修复条目开头的#NNNNN即 Bitcoin Core 主仓库的 PR 编号,可在本地git show <commit>中查看当时的完整 diff 与测试; - 主干上这些代码的「今日形态」则以上文给出的文件链接为准,例如 net_processing.cpp、walletdb.cpp、wallet.h、rawtransaction.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 的条目为准,未引入任何外部来源的推测性结论。
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 StartedRust0625
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