Zcash 5.5.1 发布说明深度解析:隐私策略崩溃修复与 ZIP 317 手续费校准

发布时间:2026/9/18 17:25:07
Zcash 5.5.1 发布说明深度解析:隐私策略崩溃修复与 ZIP 317 手续费校准 Zcash 5.5.1 发布说明深度解析隐私策略崩溃修复与 ZIP 317 手续费校准【免费下载链接】zcashZcash - Internet Money项目地址: https://gitcode.com/GitHub_Trending/zc/zcash导读Zcash 5.5.1 是 5.5.x 系列的一个补丁版本核心解决两个直接影响节点与钱包可用性的缺陷其一当用户在构造会产生透明transparent找零的交易、而隐私策略未包含AllowRevealedRecipients时节点可能直接崩溃其二ZIP 317 惯例手续费conventional fee在花费 UTXO 场景下被低估可能导致交易延迟打包甚至被卡在内存池。本文以 doc/release-notes/release-notes-5.5.1.md 为骨架结合钱包交易构造器WalletTxBuilder与 ZIP 317 的源码实现剖析这两个问题的成因、修复方式以及背后的交易构造与手续费计算机制帮助读者理解 Zcash 钱包交易管线在 5.5.1 之后的行为。一、发布背景5.5.1 的定位与改动范围Zcash 5.5.1 是一个面向稳定性的补丁版本改动集中体现在src/wallet/wallet_tx_builder.cpp、src/wallet/wallet_tx_builder.h与src/zip317.cpp等文件上。从 Changelog 来看本次全部提交均出自 Greg Pfeil共 10 项主要包括为WalletTxBuilder补充 legacy 账户与 ZIP 317 手续费的测试修复获取找零地址change address时的错误处理修正交易创建tx creation阶段对vin输入的手续费计算更早地计算consensusBranchId共识分支 ID推迟部分被阻塞的依赖升级发布流程相关的版本号与 manpage 更新。值得注意的是5.5.1 的改动并不是引入新特性而是围绕 5.5.0 引入的WalletTxBuilder交易构造管线进行 bug 修复——这直接决定了本次修复与测试都集中在这一新架构上。二、修复一隐私策略不含AllowRevealedRecipients时的崩溃2.1 问题描述当用户发起一笔会产生透明找零transparent change的交易而所选的privacyPolicy不包含AllowRevealedRecipients时节点可能崩溃。这意味着在隐私策略严格例如默认的FullPrivacy的前提下钱包在尝试为交易选择一个透明找零地址的过程中遇到错误路径却未做妥善处理最终导致节点进程异常退出。2.2 底层原因找零地址解析的错误处理缺陷从源码看找零地址的生成集中在WalletTxBuilder::GetChangeAddresssrc/wallet/wallet_tx_builder.cpp中。该函数根据发送方选择器ZTXOSelector的模式透明地址、Sapling 地址、统一地址 UA、账户模式等分派到不同的找零地址生成逻辑例如透明选择器调用changeAddressForTransparentSelector其中通过getAllowedChangePools判断只有当strategy.AllowRevealedRecipients()为真时才允许把透明池OutputPool::Transparent纳入可选的找零池否则透明找零被拒绝统一账户AccountZTXOPattern调用wallet.GenerateChangeAddressForAccount若账户不允许透明找零则返回TransparentChangeNotPermitted。关键在于错误映射error mapping部分src/wallet/wallet_tx_builder.cpp - tl::expectedChangeAddress, AddressResolutionError { return wallet.GenerateChangeAddressForAccount( acct.GetAccountId(), getAllowedChangePools(acct.GetReceiverTypes())) .map_error([](auto err) { switch (err) { case AccountChangeAddressFailure::DisjointReceivers: return AddressResolutionError::CouldNotResolveReceiver; case AccountChangeAddressFailure::TransparentChangeNotPermitted: return AddressResolutionError::TransparentChangeNotAllowed; case AccountChangeAddressFailure::NoSuchAccount: throw std::runtime_error(Selector account doesn’t exist.); } }); }可以推断5.5.1 之前的版本在“需要生成找零地址、但隐私策略禁止透明找零”这条路径上没有完整覆盖所有错误分支导致在特定选择器模式下错误被当作成功处理或落入未定义分支从而引发崩溃。5.5.1 通过“处理获取账户找零地址时的错误”Handle errors when getting change addr for account这一提交修复了该缺陷并在AddressResolutionError枚举src/wallet/wallet_tx_builder.h中明确了相关的错误语义//! Requested PrivacyPolicy doesn’t include AllowRevealedRecipients TransparentRecipientNotAllowed, //! Requested PrivacyPolicy doesn’t include AllowRevealedRecipients TransparentChangeNotAllowed,2.3 隐私策略如何参与交易构造崩溃修复的核心在于TransactionStrategy对“允许揭示什么”的严格建模定义见 src/wallet/wallet.h。PrivacyPolicy是一个按严格度排列的格lattice取值依次为策略含义FullPrivacy完全隐私不揭示收款人、金额、发送方不链接账户AllowRevealedAmounts允许揭示金额AllowRevealedRecipients允许揭示收款人例如允许透明收款方AllowRevealedSenders允许揭示发送方AllowFullyTransparent允许完全透明交易AllowLinkingAccountAddresses允许在交易中链接同一账户的多个地址NoPrivacy不要求任何隐私策略通过TransactionStrategy::FromStringsrc/wallet/wallet.cpp解析并被 RPC 层使用src/wallet/rpcwallet.cppauto strategyName specifiedPolicy.value(); if (strategyName LegacyCompat) { // ... 映射为兼容旧行为的策略 } else { strategy TransactionStrategy::FromString(strategyName); if (!strategy.has_value()) { // Unknown privacy policy name } }各 RPC 的默认策略不同从 src/wallet/rpcwallet.cpp 的 RPC 帮助文本可以看到z_sendmany默认privacyPolicyLegacyCompat第 4892 行z_shieldcoinbase默认privacyPolicyAllowRevealedSenders第 5256 行z_mergetoaddress默认privacyPolicyLegacyCompat第 5447 行。在交易构造过程中AllowRevealedRecipients()的判定贯穿始终收款方解析ResolvePayment中P2PKH/P2SH 透明收款方只有在strategy.AllowRevealedRecipients()为真时才被接受否则返回TransparentRecipientNotAllowedsrc/wallet/wallet_tx_builder.cpp找零池选择如上所述透明找零只有在策略允许揭示收款人时才被加入候选池最终校验TransactionEffects::ApproveAndBuild通过GetRequiredPrivacyPolicy()计算交易实际所需的最严格策略再用strategy.IsCompatibleWith(requiredPrivacy)检查用户指定策略是否足够宽松不满足时返回明确错误src/wallet/wallet_tx_builder.cppauto requiredPrivacy this-GetRequiredPrivacyPolicy(); if (!strategy.IsCompatibleWith(requiredPrivacy)) { return TransactionBuilderResult(strprintf( The specified privacy policy, %s, does not permit the creation of the requested transaction. Select %s to allow this transaction to be constructed., strategy.PolicyName(), TransactionStrategy::ToString(requiredPrivacy) (requiredPrivacy PrivacyPolicy::NoPrivacy ? : or weaker))); }也就是说修复后的行为是当隐私策略过严时z_sendmany等命令会返回一段可读的错误信息提示需要选择AllowRevealedRecipients或更宽松的策略而不是让节点崩溃。这正是 5.5.1 第一个修复项的预期效果把“崩溃”降级为“可解释的 RPC 错误”。2.4 测试佐证Changelog 中的两项提交直接对应本修复的验证Add a test for WalletTxBuilder with legacy account——覆盖 legacy 账户模式下的找零地址生成Test that WalletTxBuilder uses correct ZIP 317 fee——覆盖手续费正确性。这些测试位于钱包的 gtest 套件中例如 src/wallet/gtest/test_rpc_wallet.cpp 中大量以WalletTxBuilder builder(Params(), minRelayTxFee)构造实例的用例第 101、195、300、466 行等以及测试中对tx.GetConventionalFee()与预期惯例手续费的比较第 47-51 行用于保证修复后的手续费与找零行为符合规范。三、修复二ZIP 317 惯例手续费对 UTXO 花费的低估3.1 ZIP 317 手续费模型回顾ZIP 317 以“逻辑动作数”logical actions为基础计算惯例手续费。核心常量定义在 src/zip317.h// Constants for fee calculation. static const CAmount MARGINAL_FEE 5000; // 每个动作的边际费用zatoshi static const size_t GRACE_ACTIONS 2; // 宽限动作数 static const size_t P2PKH_STANDARD_INPUT_SIZE 150; // 标准 P2PKH 输入字节数 static const size_t P2PKH_STANDARD_OUTPUT_SIZE 34; // 标准 P2PKH 输出字节数 ... /// This is the lowest the conventional fee can be in ZIP 317. static const CAmount MINIMUM_FEE MARGINAL_FEE * GRACE_ACTIONS; // 10000 zat手续费计算公式src/zip317.cppCAmount CalculateConventionalFee(size_t logicalActionCount) { return MARGINAL_FEE * std::max(GRACE_ACTIONS, logicalActionCount); }即conventional_fee 5000 * max(2, logical_actions)最低手续费为 10000 zatoshi。逻辑动作数src/zip317.cppsize_t CalculateLogicalActionCount( const std::vectorCTxIn vin, const std::vectorCTxOut vout, unsigned int joinSplitCount, unsigned int saplingSpendCount, unsigned int saplingOutputCount, unsigned int orchardActionCount) { const size_t tx_in_total_size GetTxIOFieldSize(vin); const size_t tx_out_total_size GetTxIOFieldSize(vout); return std::max(ceil_div(tx_in_total_size, P2PKH_STANDARD_INPUT_SIZE), ceil_div(tx_out_total_size, P2PKH_STANDARD_OUTPUT_SIZE)) 2 * joinSplitCount std::max(saplingSpendCount, saplingOutputCount) orchardActionCount; }逻辑动作数取“透明输入折算的动作数”与“透明输出折算的动作数”中的较大者再加上 Sprout joinsplit、Sapling spend/output、Orchard action 的折算。因此透明输入UTXO的数量与大小直接影响逻辑动作数进而影响惯例手续费——这正是本次“低估”问题的根源。3.2 低估问题的成因5.5.1 之前的CalcZIP317Fee定义于 src/wallet/wallet_tx_builder.cpp在估算手续费时对透明输入的序列化大小处理存在偏差。该函数为估算而构造vin时使用了DummySignatureCreator生成占位签名数据for (const auto utxo : inputs.value().utxos) { SignatureData sigdata; ProduceSignature( DummySignatureCreator(wallet), utxo.tx-vout[utxo.i].scriptPubKey, sigdata, consensusBranchId); vin.emplace_back(COutPoint(utxo.tx-GetHash(), utxo.i), sigdata.scriptSig); }如果占位签名scriptSig的尺寸与实际签名如 P2PKH 的 107 字节左右的 DER 签名 公钥 script 前缀存在差距就会导致GetTxIOFieldSize(vin)偏小进而使CalculateLogicalActionCount低估动作数、CalculateConventionalFee低估惯例手续费。手续费低估的后果正如发布说明所述矿工按 ZIP 317 规则打包交易时会要求交易支付不低于惯例手续费的费率若钱包支付的手续费低于惯例费用交易在区块模板中被“欠费动作”unpaid actions机制处理相关限制常量见 src/zip317.hDEFAULT_BLOCK_UNPAID_ACTION_LIMIT、DEFAULT_TX_UNPAID_ACTION_LIMIT结果就是交易延迟打包甚至被卡在内存池中。5.5.1 的 Correct fee calculation for vin in tx creation 提交修正了这一估算使花费 UTXO 时的手续费计算与实际序列化后的交易一致。配套的 Extract fee check from test 提交把手续费校验逻辑从测试中提取为可复用的检查进一步保证该问题不会回归。3.3 修复后手续费如何被使用在WalletTxBuilder::PrepareTransactionsrc/wallet/wallet_tx_builder.cpp中手续费有两种来源调用方显式传入feestd::optionalCAmount此时直接使用该固定值但会先经过MaxFeeError超过maxTxFee、InvalidFeeError超出MoneyRange与AbsurdFeeError超过惯例手续费 4 倍WEIGHT_RATIO_CAP等校验未传fee默认按 ZIP 317 计算进入IterateLimitsrc/wallet/wallet_tx_builder.cpp的迭代过程先用仅含收款输出的费用做下界再循环执行LimitToAmount选择输入、用GetConstrainedFee重新计算含输入与找零后的费用直到费用收敛updatedFee previousFee。auto previousFee MINIMUM_FEE; auto updatedFee GetConstrainedFee(wallet, std::nullopt, resolved.GetResolvedPayments(), std::nullopt, consensusBranchId); ... do { spendableMut spendable; targetAmount sendAmount updatedFee; bool foundSufficientFunds spendableMut.LimitToAmount( targetAmount bumpTargetAmount, dustThreshold, resolved.GetRecipientPools()); changeAmount spendableMut.Total() - targetAmount; ... previousFee updatedFee; updatedFee GetConstrainedFee(...); } while (updatedFee ! previousFee);GetConstrainedFee再调用CWallet::ConstrainFeesrc/wallet/wallet_tx_builder.cpp在惯例手续费与最低中继费率minRelayFee之间取约束后的值。这保证了最终入池的交易既满足 ZIP 317 惯例费率又不会低于网络中继门槛。修复后的 5.5.1 中由于vin尺寸估算准确IterateLimit的收敛结果与实际构建出的交易一致交易不会再因为“费用低于惯例费用”而被矿工拒之门外。四、修复影响与升级建议4.1 对运行者的影响稳定性修复了特定隐私策略组合下构造找零地址导致的节点崩溃任何升级到 5.5.1 的节点都应包含此修复交易成功率修复了 UTXO 花费场景下 ZIP 317 手续费低估钱包发送的透明输入交易将更可能被立即打包可观测性隐私策略不兼容时RPC 现在返回描述性错误提示选择AllowRevealedRecipients或更宽松策略便于用户排查。4.2 对开发者的影响WalletTxBuildersrc/wallet/wallet_tx_builder.h是 5.5.x 中统一交易构造的入口PrepareTransaction→ApproveAndBuild的流程现在在找零、费用、隐私策略三方面都有更严格的校验与测试覆盖需要为钱包实现自定义交易构造逻辑的开发者应以CalcZIP317Fee/CalculateLogicalActionCountsrc/zip317.cpp为准计算惯例手续费避免重复此前的低估 bug新增的隐私策略校验逻辑GetRequiredPrivacyPolicy与IsCompatibleWith可作为参考实现“先规划、后校验、再构建”的安全交易构造模式。4.3 升级路径本仓库为只读镜像如需应用该版本请从官方 Zcash 发布渠道获取 5.5.1 对应的发布包或源码 tag在现有 5.5.x 节点上执行常规升级流程先备份钱包与数据目录再替换二进制并重启。5.5.1 不含共识规则变更属于可热升级的补丁版本但仍建议在测试网先行验证。五、总结Zcash 5.5.1 虽是小版本却解决了两个直接影响用户体验的问题一是把“隐私策略过严导致找零地址无法生成”从节点崩溃变为可控错误二是校准了 UTXO 花费场景下 ZIP 317 惯例手续费的计算避免交易因费率不足而被延迟打包。两者都服务于同一目标让WalletTxBuilder这条新交易构造管线在隐私约束下既安全又可靠。对于运行 5.5.0 及更早 5.5.x 的节点与钱包服务建议尽快升级。参考文件发布说明doc/release-notes/release-notes-5.5.1.md钱包交易构造器头文件src/wallet/wallet_tx_builder.h钱包交易构造器实现src/wallet/wallet_tx_builder.cppZIP 317 常量与接口src/zip317.hZIP 317 手续费计算src/zip317.cpp隐私策略定义src/wallet/wallet.h隐私策略解析src/wallet/wallet.cppRPC 层策略接入与默认值src/wallet/rpcwallet.cpp钱包侧测试src/wallet/gtest/test_rpc_wallet.cpp【免费下载链接】zcashZcash - Internet Money项目地址: https://gitcode.com/GitHub_Trending/zc/zcash创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考