Bitcoin Core 29.4 维护版深度解析:chainstate 数据库定期压缩与一批稳定性修复
Bitcoin Core 29.4 维护版深度解析chainstate 数据库定期压缩与一批稳定性修复【免费下载链接】bitcoinBitcoin Core integration/staging tree项目地址: https://gitcode.com/GitHub_Trending/bi/bitcoin本文基于本仓库doc/release-notes/release-notes-29.4.md发布说明结合src/下实现源码逐项拆解 Bitcoin Core 29.4 这一维护版本的核心变更链状态chainstate数据库的“反复重写大量数据”缺陷如何被修复、LevelDB 的 seek compaction 为何被禁用、以及钱包、构建、CI 与文档层面的多项修正。读完你既能掌握 29.4 的升级路径与兼容性边界也能从源码层面理解 UTXO 集落盘与压缩的真实工作机制。版本总览这是一个“修内功”的维护版Bitcoin Core 29.4 属于 29.x 系列的功能性维护版本官方发布说明即仓库中的 doc/release-notes/release-notes-29.4.md明确指出本次发布包含各种缺陷修复与性能改进以及更新的翻译。本仓库根目录的 README.md 表明其定位为 Bitcoin Core 的 integration/staging tree29.4 的改动正对应同步进主线的这些合并请求。从补丁分布看29.4 的变更集中在Validation验证2 个补丁其中就包含本次最大的看点——chainstate 定期压缩Leveldb禁用 seek compaction1 个上游仓库补丁Net / Wallet / Build / Test / Doc / CI / Misc各 14 个小补丁。整个版本没有引入新的共识规则或 RPC 行为变化属于典型的“稳定性 工程质量”发布。以下按“能直接影响你节点磁盘 I/O 的核心变更 → 其余分项修复”顺序展开。如何升级到 29.4发布说明给出了标准的三平台升级流程保持与既有维护版一致先彻底关停旧版本节点并等待进程完全退出。由于需要把内存中的脏缓存刷盘、可能进行数据目录迁移某些情况下关闭过程可能持续几分钟切勿在中途强行结束进程。Windows直接运行安装程序installer覆盖安装。macOS将新的/Applications/Bitcoin-Qt覆盖到旧程序。Linux覆盖bitcoind/bitcoin-qt可执行文件即可。两点补充说明与发布说明一致支持从已到达 EOL生命周期结束的旧版本直接升级但若数据目录需要迁移首次启动可能耗时较长旧钱包版本在一般情况下仍然受支持即老钱包数据文件无需特殊转换即可被 29.4 加载。兼容性受支持与测试的操作系统发布说明声明的支持边界为系统版本要求LinuxKernel 3.17macOSmacOS 13WindowsWindows 10绝大多数其他类 Unix 系统理论上可运行但官方测试频率较低官方明确不建议在不支持的系统上运行 Bitcoin Core。对于运行节点的生产环境建议优先匹配上表。构建细节可参考本仓库 INSTALL.md 及各平台独立指南如 doc/build-freebsd.md。核心看点修复 chainstate 数据库“反复重写自身大块数据”发布说明在 Notable changes 中给出了一句关键描述本版本修复了一个问题chainstate 数据库在正常运行时反复重写自身的大块数据导致过量的磁盘读写。这句话直接点出了 29.4 最重要的性能修复。它由三个 PR 协同完成下面从源码层面还原其来龙去脉。为什么会出现“反复重写”——LevelDB 的 seek compaction 机制chainstateUTXO 集与区块、交易索引一样底层由嵌入的 LevelDB 存储。LevelDB 的 compaction压缩分为两类size compaction大小压缩当某层文件总大小超过阈值时触发把低层小文件合并成大文件属于写入驱动的常规维护seek compaction寻道压缩当某层文件因随机读seek次数累积到一定阈值时主动重写该文件把热点数据提升到更易读的位置。本仓库内嵌 LevelDB 的 src/leveldb/db/version_set.cc 保留了相关注释与逻辑// conservative and allow approximately one seek for every 16KB // of data before triggering a compaction. // // Note: seek compactions are disabled. See Version::UpdateStats. f-allowed_seeks static_castint((f-file_size / 16384U)); if (f-allowed_seeks 100) f-allowed_seeks 100;注释中seek compactions are disabled. See Version::UpdateStats即对应发布说明中的#61 (bitcoin-core/leveldb): Disable seek compaction。其风险在于UTXO 集是典型的“大量随机读取 少量写入”负载如果高频的随机查询反复命中同一批 SSTable 文件seek compaction 会不断把这些文件重写到新的层级布局造成发布说明所描述的“normal operation 期间过量磁盘读写”。与之配套的两个回归测试也写明了行为契约src/leveldb/db/db_test.cc 中的GetDoesNotTriggerSeekCompactionseek compaction 在此 fork 中禁用因此重复读取不得改变层级布局而手动压缩仍须正常工作src/leveldb/db/autocompact_test.cc连续读取 100 次后被读区间的文件大小不得缩小——即读操作永远不会调度压缩。也就是说29.4 起LevelDB 层的“读导致写”这一自我放大行为被彻底掐断。配套的正向维护chainstate 按概率定期压缩仅仅关掉 seek compaction 还不够——UTXO 集中长期失效的数据仍需要一种受控的回收机制这正是 PR#35465 coins: compact chainstate regularly的职责把“压缩时机”从 LevelDB 内部的隐式触发改为 Bitcoin Core 验证层显式、有节制地调度。在 src/validation.cpp 中可以看到决定是否压缩的判定函数// Return whether the completed full flush should compact chainstate static bool ShouldCompactChainstate(bool in_ibd) { static constexpr uint32_t flush_ratio{320}; // Roughly every 2 weeks with hourly flushes return !in_ibd FastRandomContext().randrange(flush_ratio) 0; }关键实现事实压缩只在一次完整的全量落盘full flush结束后才可能发生避免与常规写入争抢采用1/320 的随机概率randrange(flush_ratio) 0若按约每小时一次的 flush 频率估算平均大约每两周触发一次整库压缩同步阶段Initial Block DownloadIBD中不触发——刚同步完的 chainstate 本身布局较新无需压缩也避免拖慢同步。调度点在FlushStateToDisk的全量 flush 分支中src/validation.cppif (full_flush_completed) { ... if (!m_chainman.m_interrupt ShouldCompactChainstate(m_chainman.IsInitialBlockDownload())) { try { CoinsDB().CompactFullAsync(); } catch (const std::exception e) { LogWarning(Failed to start chainstate compaction (%s), e.what()); } } }后台异步压缩的实现CompactFullAsync真正执行压缩的是CCoinsViewDB::CompactFullAsync()src/txdb.cpp。它被要求在持有cs_main时调用内部通过std::async在一次性后台线程中执行避免阻塞主验证线程m_compaction std::async(std::launch::async, [this] { try { util::ThreadRename(utxocompact); LOCK(m_db_mutex); LogDebug(BCLog::COINDB, Starting chainstate compaction of %s, ...); m_db-CompactFull(); ... } catch (const std::exception e) { LogWarning(Failed chainstate compaction (%s), e.what()); } }).share();几个值得注意的工程细节线程被命名为utxocompact方便运维通过线程名定位内部加m_db_mutex与 src/txdb.h 声明的缓存重分配互斥防止压缩期间ResizeCache()替换底层CDBWrapper造成竞态返回std::shared_futurevoid多个调用方可共享等待。若上一次压缩尚未结束直接复用同一个 futurem_compaction不会叠加多个压缩任务析构函数中若检测到后台压缩未完成会打印Waiting for background chainstate compaction of ...并阻塞等待其结束保证正常退出时数据一致src/txdb.cpp。最底层调用CompactFull()src/dbwrapper.cpp等价于对整个键空间的CompactRange(nullptr, nullptr)void CDBWrapper::CompactFull() { DBContext().pdb-CompactRange(nullptr, nullptr); }用测试“看见”压缩效果仓库的单元测试 src/test/coins_tests.cppcoins_db_leveldb_layout直观地验证了压缩的行为它通过 LevelDB 属性leveldb.num-files-at-level2统计第二层文件数先断言初始为 0然后执行WITH_LOCK(::cs_main, return base.CompactFullAsync()).wait(); BOOST_CHECK_EQUAL(level2_files(base), 1);——一次完整的后台压缩后第二层恰好收敛为 1 个文件并且其中的 coin 与 best block 数据完好可读。这从测试侧印证了“定期整库压缩能把散落数据合并为紧凑布局”的效果。如果你希望对节点施加一次即时压缩可以关注 src/dbwrapper.cpp 中由DBParams.options.force_compact控制的路径——打开数据库时即执行一次全量压缩对应相关启动选项/配置属于运维侧的可选手段。小结29.4 对磁盘 I/O 的实际影响把以上三点拼起来29.4 修复 chainstate 异常磁盘写放大的完整链路是LevelDB 层面禁用 seek compaction#61杜绝“读热点 → 重写文件”的自我放大循环这是消除“反复重写大块数据”的关键验证层引入 1/320 概率、非 IBD 期间触发的显式定期全量压缩#35465在 flush 完成后的后台线程中执行保证 UTXO 存储长期保持紧凑整套行为被测试锁定读不改变层级src/leveldb/db/autocompact_test.cc、压缩收敛层级布局src/test/coins_tests.cpp。从源码结构可以推断受益面最大的是长期运行、持续处理随机 UTXO 查询的主网全节点其运行期磁盘写入应明显下降而存储布局仍会由周期压缩保持健康。Validation#35209 修正预计算交易数据的生命周期第二个 Validation 补丁#35209 validation: correct lifetime of precomputed tx data属于内存安全/正确性修复与本仓库 29.4 代码中 src/validation.cpp 的实现直接对应CBlockUndo blockundo; // Precomputed transaction data pointers must not be invalidated // until after control has run the script checks (potentially // in multiple threads). Preallocate the vector size so a new allocation // doesnt invalidate pointers into the vector, and keep txsdata in scope // for as long as control. std::vectorPrecomputedTransactionData txsdata(block.vtx.size()); std::optionalCCheckQueueControlCScriptCheck control; if (auto queue m_chainman.GetCheckQueue(); queue.HasThreads() fScriptChecks) control.emplace(queue);理解这个补丁需要先了解PrecomputedTransactionData它是签名/脚本校验的“预计算数据”BIP341 taproot 所需的hashPrevouts/hashAmounts等单次或双重 SHA256 中间量定义见 src/script/interpreter.h在校验同一笔交易的多个输入时可复用避免重复计算哈希。修复的实质是生命周期lifetime问题ConnectBlock会把各笔交易的预计算数据指针交给CScriptCheck定义于 src/validation.h持有PrecomputedTransactionData* txdata指针脚本检查可能在CCheckQueueControl管理下的多线程工作队列中并发执行若保存这些对象的容器在脚本检查尚未结束时就因扩容reallocation而移动了元素指向旧位置的指针就会悬空——这是典型的使用已失效指针use-after-move/use-after-free 类隐患修复方式是按区块交易数预先分配好容量std::vectorPrecomputedTransactionData txsdata(block.vtx.size());确保后续写入元素时 vector 不再触发重新分配同时让txsdata的生命周期覆盖control运行脚本检查的整个过程。从代码层面看这一改动消除了并发脚本校验路径上对容器地址稳定性的依赖属于保障长稳运行的正确性加固。相关的脚本缓存校验行为可进一步参考 src/test/txvalidationcache_tests.cpp其中同样以栈上PrecomputedTransactionData贯穿多轮CheckInputScripts调用。Net#34093 修复默认网关查询的编译告警#34093 netif: fix compilation warning in QueryDefaultGatewayImpl()对应 src/common/netif.cpp 中的默认网关探测实现。该文件按平台分别实现Linux含 IPv4/IPv6src/common/netif.cpp 起通过AF_NETLINK的NETLINK_ROUTEsocket 发起RTM_GETROUTE查询注意代码中对 Linux 与 FreeBSD 的NLM_F_DUMP、RTM_F_PREFIX标志做了条件编译区分Windowssrc/common/netif.cpp 起使用GetBestRoute2查询到默认网关的最佳路由macOSsrc/common/netif.cpp 起通过sysctl读取PF_ROUTE路由表并遍历rt_msghdr消息寻找RTAX_GATEWAY其他平台则落到返回std::nullopt的 dummy 实现src/common/netif.cpp。该函数服务于出站连接的默认出口探测用于确定是否使用代理等场景本补丁仅消除特定平台编译时的告警不影响行为语义。Wallet#35228 估算输入体积时使用真实 outpoint#35228 wallet: use outpoint when estimating input size改善了钱包在构造/预估交易时的输入体积进而影响手续费估算精度。其代码落点在 src/wallet/spend.cppint CalculateMaximumSignedInputSize(const CTxOut txout, const COutPoint outpoint, const SigningProvider* provider, bool can_grind_r, const CCoinControl* coin_control) { if (!provider) return -1; if (const auto desc InferDescriptor(txout.scriptPubKey, *provider)) { if (const auto weight MaxInputWeight(*desc, CTxIn{outpoint}, coin_control, true, can_grind_r)) { return static_castint(GetVirtualTransactionSize(*weight, 0, 0)); } } return -1; }核心变化是把具体花费的COutPoint传入MaxInputWeight使“最大输入体积”的推导能结合该输入真实的 outpoint 信息例如基于描述符推断出的输入构造而不是仅凭脚本模板做泛化估计。旧路径在估算时未能利用 outpoint 细节。在预选输入的处理路径上src/wallet/spend.cpp外部输入也会携带 outpoint 走带完整参数的估算分支if (input_bytes -1) { input_bytes CalculateMaximumSignedInputSize(txout, outpoint, coin_control.m_external_provider, can_grind_r, coin_control); }从代码结构可以推断这一改动提升了 fee bump / 找零计算前“预估输入字节数”的准确性从而让手续费估算更贴近最终签名后交易的实际情况。若某些输入因无法求解而估算失败返回 -1上层会报出Not solvable pre-selected input ...之类的错误src/wallet/spend.cpp属于既有行为。Build两项构建系统修正#34228gen_id 中取消继承 SOURCE_DATE_EPOCH依赖构建脚本 depends/gen_id 中在输出工具链 ID 前显式清除了环境变量# Unset SOURCE_DATE_EPOCH to prevent it from leaking into tool # outputs and to maximize reuse of the built package cache. unset SOURCE_DATE_EPOCHSOURCE_DATE_EPOCH是 GNU 可复现构建标准中用于固定时间戳的环境变量若其值渗入某些工具例如 gcc 的__DATE__/__TIME__或压缩工具时间戳的输出会导致同一工具链在不同时间生成的内容 ID 漂移。此改动通过 unset 它一方面避免 ID 被无关的时间因素污染另一方面最大化 depends 预构建包缓存的复用命中率进而加速构建与 CI。#34848CMake 迁移废弃的 SQLite3 target29.x 系列已全面转向 CMake 构建体系见根目录 CMakeLists.txt 与 CMakePresets.json。本补丁在 CMake 侧迁离了新版 CMake 中已废弃的SQLite3target 用法改为使用新的引用方式以适配更新的 CMake 版本并消除 deprecation 告警避免未来 CMake 升级导致构建失败。该改动不影响-DWITH_SQLITE之类的既有构建开关语义。Test#34918 清理 fuzz 中未使用的全局指针#34918 fuzz: [refactor] Remove unused g_setup pointers是一次纯重构删除 fuzz 目标中不再使用的g_setup全局指针变量。fuzz 构建与运行入口可参考 ci/lint/requirements.txt 与 test/fuzz/coins_view.cpp该文件里CCoinsViewDB::CompactFullAsync也出现在 fuzz 操作的候选动作中此类清理旨在减少全局状态与未使用变量提升 fuzz 代码的可维护性与覆盖率工具的准确性不产生行为变化。Doc四处文档修正本版本打包了四个文档层面的修正均不影响运行行为但值得部署与开发人员留意PR内容影响#34510修复 bpftrace 安装文档链接失效涉及 contrib/tracing/README.md 一带的跟踪工具指引可读性#34561钱包 RPC 手册页中补全缺失的fee_rate参数示例修正send*/bumpfee 等 RPC 文档示例的可执行性#34671更新 Debian/Ubuntu 下的 Guix 安装指引对应 contrib/guix/INSTALL.md涉及 contrib/guix 目录下的构建流程#35283在 BSD 构建指南中提示需-DWITH_ZMQON已体现于 doc/build-freebsd.md要启用 ZeroMQ 通知需安装libzmq4 pkgconf并在配置时传入-DWITH_ZMQON其中 #35283 对 BSD 用户有实操价值ZeroMQ 通知不是默认编译进构建的必须显式-DWITH_ZMQON。ZMQ 消息协议的整体用法可参见 doc/zmq.md 与 contrib/zmq/zmq_sub.py。CI三处流水线调整#35202 ci: restore sockets in i686, no IPC job在 i686 且关闭 IPC 的 CI job 中恢复 socket 相关测试覆盖#35378 ci: switch runners from cirrus to warpbuild把 CI runner 从 Cirrus 迁移到 WarpBuild基础设施调整#35408 ci: 35378 followups上一迁移的后续收尾补丁。这些 CI 脚本改动分布在 ci/test/如 ci/test/00_setup_env_i686_no_ipc.sh下属于工程维护范畴与运行时行为无关。Misc#35175 兼容 Boost ≥ 1.91 的编译修复#35175 multi_index: fix compilation failure with boost 1.91修复了 Boost 1.91 及以上版本中boost::multi_index相关 API 变动引起的编译失败。Bitcoin Core 大量依赖 Boost 容器如交易池、addrman 等模块中的 multi_index 结构当发行版升级 Boost 后此补丁保证 29.4 仍可正常编译。Credits贡献者29.4 由以下直接贡献者合入依发布说明andrewtoth、Cory Fields、Daniel Pfeifer、darosior、fanquake、Hennadii Stepanov、jayvaliya、junbyjun1238、Lőrinc、MarcoFalke、SomberNight、ToRyVand、willcl-ark同时感谢在 Transifex 上协助翻译的所有社区成员。升级建议与总结综合来看Bitcoin Core 29.4 是一个“低成本、高确定性收益”的维护版建议升级修复了 chainstate 在正常运行期的异常磁盘写放大对长期运行的主网/测试网节点尤其有意义无任何共识或 RPC 破坏性变化升级风险低升级方式按上文三平台流程先完全关停旧进程再替换二进制首次启动如遇数据迁移请耐心等待验证要点升级后可在日志中观察utxocompact线程相关的Starting chainstate compaction ... / Finished chainstate compaction ...对应 src/txdb.cpp确认周期压缩按约两周一次的节奏正常工作。如需从源码深入后续版本差异可在本仓库 doc/release-notes/ 目录下对照 29.0、30.0、31.0 等相邻版本发布说明观察该项目在链状态存储与验证性能上的持续演进脉络。【免费下载链接】bitcoinBitcoin Core integration/staging tree项目地址: https://gitcode.com/GitHub_Trending/bi/bitcoin创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考