Zcash 5.3.1 版本解析:asOfHeight 历史高度钱包查询、Equihash 解内存裁剪与 Orchard 回滚断言修复
【免费下载链接】zcashZcash - Internet Money项目地址: https://gitcode.com/GitHub_Trending/zc/zcash
导读
Zcash 5.3.1 是一个聚焦稳定性的维护版本,核心成果有三:一是修复了重启节点时偶发的Assertion 'uResultHeight == rewindHeight' failed崩溃(issue #5958),二是通过不再把每个区块头的 Equihash 解常驻内存来显著降低zcashd的内存占用,三是为 15 个钱包状态查询 RPC 统一引入可选的asOfHeight参数,让调用方可以"回到过去"的某个区块高度执行查询。阅读本文后,你将掌握这三个变更的完整语义、底层实现原理与实战调用方式,并了解该版本在网络同步、度量指标与构建流程上的配套改进。
本文基于仓库 doc/release-notes/release-notes-5.3.1.md,并结合 src/ 下源码与 qa/rpc-tests/ 测试展开。
版本概览:5.3.1 发布要点
5.3.1 延续了 Zcash 的常规补丁节奏,主要工作集中在三块:
- 崩溃修复:解决重启节点时 Orchard 钱包回滚相关的断言失败(#5958);
- 内存优化:
CBlockIndex不再长期持有 Equihash 解,写入 leveldb 后即裁剪; - RPC 增强:为查询钱包状态的 RPC 方法批量新增
asOfHeight参数,支持按历史高度执行查询。
除此之外,本次版本还合入了并行区块下载调度优化、头部同步超时机制、-debugmetrics配置项、Gitian 构建版本字符串修复以及若干测试与开发工具改进(完整提交列表见原 Changelog 一节)。
核心修复:uResultHeight == rewindHeight断言失败(#5958)
问题场景
原文档指出,本版本修复了节点重启时偶发的错误:
Assertion `uResultHeight == rewindHeight` failed该错误与 Orchard 钱包的"生日扫描优化"(wallet birthday scanning optimization)相关。Zcash 钱包在初始化扫描时,会跳过早于钱包"生日"(nTimeFirstKey对应的首个密钥时间戳减去时间窗口)的区块,从钱包创建之后的区块开始扫描。Daira Hopwood 的提交说明中明确写道,必须确保该优化"不会导致我们尝试把 Orchard 钱包回滚到其当前检查点之后的高度"——一旦回滚目标高度高于钱包当前检查点高度,回滚语义就是非法的,进而触发断言。
源码级剖析
回滚操作的核心实现在 src/wallet/orchard.h:
bool Rewind(int nBlockHeight, uint32_t& uResultHeight) { ... return orchard_wallet_rewind(inner.get(), (uint32_t) nBlockHeight, &uResultHeight); }即把 Orchard 钱包(基于 Rust 的librustzcash)回滚到nBlockHeight,实际回滚到的高度通过uResultHeight返回,二者必须一致。
在 src/wallet/wallet.cpp 的ScanForWalletTransactions()中可以看到修复后的防御性逻辑:
if (isInitScan) { int rewindHeight = std::max(nu5_height.value(), pindex->nHeight - 1); ... uint32_t uResultHeight{0}; if (orchardWallet.Rewind(rewindHeight, uResultHeight)) { // rewind was successful or a no-op, so perform Orchard wallet updates assert(uResultHeight == rewindHeight); performOrchardWalletUpdates = true; } else { // Orchard witnesses will not be able to be correctly updated, ... throw std::runtime_error("CWallet::ScanForWalletTransactions(): Orchard wallet is out of sync. Please restart your node with -rescan."); } }关键点有两个:
- 回滚目标高度被钳制为
max(NU5 激活高度, 待扫描区块高度 - 1),绝不会低于 NU5 激活高度,也绝不会高于钱包当前检查点所允许的范围; - 当回滚失败(而非结果不一致)时,给出明确的恢复指引:使用
-rescan重启节点,而不是在不确定的状态下继续运行。
此外,在区块回滚路径(src/wallet/wallet.cpp)中,每移除一个 NU5 之后的区块,都会把 Orchard 钱包回滚到前一区块高度并断言结果一致:
assert(pindex->nHeight >= 1); assert(orchardWallet.Rewind(pindex->nHeight - 1, uResultHeight)); assert(uResultHeight == pindex->nHeight - 1);实战提示:若你的节点曾在此前版本遇到该断言崩溃,升级到 5.3.1 后通常无需额外操作;若节点日志提示 Orchard 钱包失步,按提示使用zcashd -rescan(或配合-reindex,视钱包状态而定)即可恢复。
内存优化:裁剪CBlockIndex中的 Equihash 解
优化背景
Zcash 采用 Equihash 工作量证明,每个区块头都携带一个 Equihash 解(nSolution,字节数组)。此前zcashd为内存中的每一个区块头都保留一份解。对于一个运行数年、区块头数以百万计的节点,这笔内存开销相当可观,且其中的大部分解在区块确认后已不再被频繁访问。
实现机制
从 src/chain.h 的注释可以看出设计意图:
// The Equihash solution, if it is stored. Once we know that the block index // entry is present in leveldb, this field can be cleared via the TrimSolution // ... std::vector<unsigned char> nSolution;配套提供了三个关键成员:
TrimSolution()(src/chain.h):清空nSolution以节省内存,需持有cs_main锁;HasSolution()(src/chain.h):判断当前是否仍持有解,即!nSolution.empty();- 构造函数(src/chain.h)在从区块头构建索引时复制
nSolution,并递增计数器zcashd.debug.memory.allocated_equihash_solutions。
写入磁盘的一侧同样做了适配:CDiskBlockIndex在序列化时需要解数据,若内存中已被裁剪,则通过getSolution回调从 leveldb 重新读取(src/chain.h 与 src/chain.h),保证"解已持久化 → 内存可裁剪 → 需要时从磁盘取回"的完整闭环。
从源码结构可以推断,这套机制与 leveldb 中的区块索引读写路径(CBlockTreeDB)联动:一旦索引条目确认已落盘,TrimSolution()即被调用。Daira Hopwood 的提交 "Avoid storing the Equihash solution in a CBlockIndex object once it has been written to the leveldb database" 正是这一逻辑的直接证据。
可观测性:调试指标与-debugmetrics
为了让运维者能观察裁剪效果,本次版本新增了 Prometheus 指标用于跟踪 Equihash 解的内存分配情况。同时 Jack Grigg 的提交将zcashd.debug.*系列指标收敛到-debugmetrics配置选项之后(见 src/init.cpp 的帮助文本 "Include debug metrics in exposed node metrics.",默认不启用)。
实战提示:需要观测内存优化效果时,可在启动参数或 zcash.conf 中加入debugmetrics=1,即可在节点暴露的指标中看到zcashd.debug.memory.allocated_equihash_solutions等调试指标随时间下降,直观验证裁剪是否生效。
RPC 变更:asOfHeight历史高度查询
这是 5.3.1 中影响面最大、也最值得开发者关注的功能。
支持的方法一览
以下查询钱包状态的 RPC 方法新增了可选的asOfHeight参数,使查询"仿佛"在区块链处于该高度时执行:
| 方法 | 说明 |
|---|---|
getbalance | 查询总余额 |
getreceivedbyaddress | 按地址统计已收金额 |
gettransaction(*) | 查询单笔交易详情 |
getwalletinfo | 查询钱包信息 |
listaddressgroupings | 列出地址分组 |
listreceivedbyaddress(*) | 按地址列出已收款项 |
listsinceblock(*) | 列出指定区块以来的交易 |
listtransactions | 列出交易记录 |
listunspent(*) | 列出未花费输出 |
z_getbalanceforaccount | 查询统一账户余额 |
z_getbalanceforviewingkey | 按查看密钥查询余额 |
z_getmigrationstatus | 查询 Sprout 迁移状态 |
z_getnotescount | 统计笔记(note)数量 |
z_listreceivedbyaddress | 按地址列出收到的屏蔽笔记 |
z_listunspent | 列出未花费笔记/输出 |
其中带 (*) 的五个方法(gettransaction、listreceivedbyaddress、listsinceblock、listunspent)额外增加了若干参数,以保持与 Bitcoin Core 参数列表的兼容性——若要使用asOfHeight,这些额外参数必须显式传入默认值。相关实现散布于 src/wallet/rpcwallet.cpp,例如getbalance的帮助文本为:
getbalance ( "(dummy)" minconf includeWatchonly inZat asOfHeight )而gettransaction为:
gettransaction "txid" ( includeWatchonly verbose asOfHeight )参数语义与边界规则
asOfHeight的完整语义定义在 src/rpc/server.cpp 的帮助文本生成函数asOfHeightMessage()中,规则如下:
- 默认值 -1:表示当前高度(包含 mempool),与其它 RPC 中
-1的惯用含义一致;但仅支持 -1 这一个负值; - 不允许负高度:任何其它负数会直接报错
Can not perform the query as of a negative block height; - 不允许高度 0:创世区块(genesis)高度不可作为查询点,报错
Can not perform the query as of the genesis block; - "未来"高度回落:传入大于当前链高的值会被视为当前高度;
- 忽略 mempool:只要显式传入了
asOfHeight,mempool 一律被忽略,即未确认交易不会被计入结果; - minconf 联动:凡带
minconf参数的方法,当asOfHeight生效时minconf必须 ≥ 1,否则报错Require a minimum of 1 confirmation when 'asOfHeight' is provided(对应 src/rpc/server.cpp 中parseMinconf()的校验)。
核心解析函数parseAsOfHeight()(src/rpc/server.cpp)逻辑非常直白:-1视为默认(不设置可选值),< 0与0直接抛错,其余正值包装进std::optional<int>向下传递。从 Changelog 中 Greg Pfeil 的提交可以看出,实现过程中曾考虑让getinfo也支持该参数,但最终被回退,getinfo不提供asOfHeight。
底层原理:历史高度如何生效
asOfHeight的魔力在于让"链深度"的判定基于一个虚拟的链顶。关键实现在 src/wallet/wallet.cpp:
int CMerkleTx::GetDepthInMainChainINTERNAL(const CBlockIndex* &pindexRet, const std::optional<int>& asOfHeight) const { ... int effectiveChainHeight = min(chainActive.Height(), asOfHeight.value_or(chainActive.Height())); ... }即把"当前链高"替换为min(实际链高, asOfHeight),随后所有基于深度的计算(确认数、成熟度GetBlocksToMaturity、IsSpent等)都在这条虚拟链上求值。配套改动还包括:
IsTrusted()(src/wallet/wallet.cpp):当asOfHeight生效且深度为 0 时,明确"不信任 mempool 交易";- 花费判定:
IsSpent、IsSproutSpent、IsSaplingSpent、IsOrchardSpent全部支持asOfHeight(src/wallet/wallet.cpp),保证"某笔输出在历史高度是否已被花费"判定一致; - 过滤逻辑:
AvailableCoins中assert(!asOfHeight.has_value() || nMinDepth > 0)(src/wallet/wallet.cpp)在断言层面固化 minconf 约束。
另外值得注意:listunspent的实现(Kris Nuttycombe 的提交 "Addunspent_as_ofargument tolistunspent")在保持 Bitcoin Core 参数兼容的同时提供了该历史查询能力,并针对已知的 issue #6262 做了临时规避(Greg Pfeil 的 "Work around #6262 in wallet_listunspent"),相关回归测试位于 qa/rpc-tests/listtransactions.py。
实战示例
假设链高为 2000000,你想查看第 1500000 高度时的钱包总余额与某笔交易状态:
# getbalance:按 "(dummy)" minconf includeWatchonly inZat asOfHeight 的顺序传参 zcash-cli getbalance "" 1 false false 1500000 # gettransaction:按 includeWatchonly verbose asOfHeight 的顺序传参 zcash-cli gettransaction "<txid>" false true 1500000 # 带 minconf 的方法在 asOfHeight 下 minconf 必须 >= 1 zcash-cli listtransactions "*" 10 0 false 1500000调用未带asOfHeight或显式传-1时行为与旧版本完全一致;显式传入时则得到一份"历史快照"查询结果。更多 RPC 参数细节可查阅 doc/man/zcashd.1 与 doc/payment-api.md。
测试验证
该功能的测试覆盖相当完整:Greg Pfeil 的 "Add additional asOfHeight tests"、"Add error cases and default toasOfHeight",以及matured_at_height测试辅助函数(用于在测试中构造"某个高度已成熟"的 coinbase 输出)。回归测试集中在 qa/rpc-tests/listtransactions.py、qa/rpc-tests/wallet_listunspent.py 等钱包相关测试中,可结合 doc/unit-tests.md 了解运行方式。
其它值得关注的改进
网络同步:更稳健的头部与区块下载
来自 Suhas Daftuar 与 Miodrag Popović 的改动聚焦同步性能与稳定性:
- 延迟并行区块下载:在链累积足够工作量之前,不再立即启动并行区块下载,避免早期同步阶段资源浪费;
- 头部同步超时:为头部同步(headers sync)增加超时机制,超时估算改用
EstimateNetHeight(),对剩余待下载头部数量的估计更接近实际,从而更准确地判断同步是否卡死; - 活跃共识参数读取:
FindNextBlocksToDownload()改为获取活跃共识参数以读取nMinimumChainWork,避免在共识升级边界读取到过期参数。
构建与开发流程
- Gitian 构建:修复了 Gitian 版本字符串问题(回退
GIT_DIRbackport 提交、从genbuild.sh移除git_check_in_repo),并更新了 contrib/gitian-descriptors/gitian-linux-parallel.yml; - 依赖更新:aarch64 平台的 native clang 下载 URL 修复;
postponed-updates.txt中加入了 libcxx/native_clang 15.0.6 的延期更新记录(见 qa/zcash/postponed-updates.txt); - 测试框架:修复
show_helpRPC 测试对 CPU 核数的依赖,以及测试框架对 Python 3.9 的兼容问题(影响receivedby扩展 RPC 测试); - 工具链:
updatecheck脚本改进 GitHub 认证与 token 处理,支持 XDG 约定的 token 存放位置。
升级与参考
- 阅读完整提交清单与贡献者归属,见 doc/release-notes/release-notes-5.3.1.md;
- 涉及的关键源码:RPC 参数解析 src/rpc/server.cpp、钱包 RPC 实现 src/wallet/rpcwallet.cpp、历史高度深度计算 src/wallet/wallet.cpp、区块索引与解裁剪 src/chain.h、Orchard 回滚 src/wallet/orchard.h;
- 相关测试:qa/rpc-tests/listtransactions.py、qa/rpc-tests/wallet_listunspent.py;
- 升级前建议先阅读 doc/release-notes.md 了解版本线整体节奏,并对钱包数据做好备份;若遇到 Orchard 失步提示,按提示以
-rescan重启即可。
【免费下载链接】zcashZcash - Internet Money项目地址: https://gitcode.com/GitHub_Trending/zc/zcash
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考