Bitcoin Core 内存池尺寸语义重构:vsize、vsize_adjusted 与 vsize_bip141 完整指南
【免费下载链接】bitcoinBitcoin Core integration/staging tree项目地址: https://gitcode.com/GitHub_Trending/bi/bitcoin
本文基于 Bitcoin Core 的发布说明 release-notes-32800.md,讲解 mempool 相关 RPC 输出中尺寸字段的一次重要语义修正:getrawmempool、getmempoolentry、testmempoolaccept、submitpackage四个 RPC 新增vsize_adjusted与vsize_bip141字段,原先的vsize被标记为废弃(DEPRECATED),getrawtransaction则新增vsize_adjusted字段。读完后,你将能够正确区分“sigop 调整后的策略尺寸”与“BIP 141 原始虚拟尺寸”,写出不再依赖废弃字段、可平滑升级的尺寸计算逻辑。
一、为什么要修正 vsize:一次历史描述错误的代价
在引入本次改动之前,vsize在 RPC 帮助文本中被描述为 “BIP 141 virtual size”,但它的实际取值是sigop 调整后的虚拟尺寸(sigops-adjusted vsize)——两者只有在交易 sigop 成本很低时恰好相等。对于包含大量 P2SH/P2WSH 脚本 sigop 的交易,sigop 调整会放大计算尺寸,导致:
- 按
vsize计算费率(feerate)的脚本得到偏高的分母,费率被低估; - 依赖“BIP 141 vsize”语义做策略判断(如手续费估算、RBF 比较)的工具与实际行为不符。
release-notes-32800.md 明确指出了这一点:
While
vsizeis marked as DEPRECATED, it was previously erroneously described as the BIP 141 vsize, but is actually sigops-adjusted vsize. Usevsize_bip141to actually get that behavior or switch to the explicitvsize_adjustedfor retained behavior.
因此本次改动的核心是“把语义显式化”:保留旧字段以维持向后兼容,同时新增两个名字自解释的字段,让调用方按真实语义选择。
二、两个字段的精确定义与计算公式
要理解新字段的差别,需要回到 BIP 141 重量与 sigop 调整的定义。核心实现在 policy.cpp:
int64_t GetSigOpsAdjustedWeight(int64_t weight, int64_t sigop_cost, unsigned int bytes_per_sigop) { return std::max(weight, sigop_cost * bytes_per_sigop); } int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost, unsigned int bytes_per_sigop) { return (GetSigOpsAdjustedWeight(nWeight, nSigOpCost, bytes_per_sigop) + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR; }由这两个函数可以推出两个字段的确切语义:
| 字段 | 含义 | 计算方式(从源码结构看) |
|---|---|---|
vsize_bip141 | 原始 BIP 141 虚拟尺寸,witness 数据按 1/4 折扣,不含 sigop 调整 | ceil(weight / 4),即GetVirtualTransactionSize(tx)在 sigop_cost 取 0 时的值 |
vsize_adjusted | 策略实际使用的 sigop 调整后尺寸,mempool 策略与 RPC 尺寸统计以此为分母 | ceil(max(weight, sigop_cost × nBytesPerSigOp) / 4),其中nBytesPerSigOp由-bytespersigop启动参数控制,默认值为 1 |
可以推断:对普通低 sigop 交易,vsize_adjusted == vsize_bip141;当交易 P2SH 嵌套 sigop 成本较高(sigop_cost × bytespersigop 超过 weight)时,vsize_adjusted > vsize_bip141。这正是要在费率计算中注意的差异来源。
三、受影响的 RPC 逐一解析
1. getrawmempool(verbose)与 getmempoolentry
两者共用 mempool 条目的 JSON 序列化逻辑entryToJSON,字段写入位于 mempool.cpp:
info.pushKV("vsize_adjusted", e.GetTxSize()); info.pushKV("vsize", e.GetTxSize()); info.pushKV("vsize_bip141", GetVirtualTransactionSize(e.GetTx())); info.pushKV("weight", e.GetTxWeight());注意实现上的一个关键细节:vsize_adjusted与废弃的vsize都取自同一个e.GetTxSize(),而vsize_bip141是对原始交易重新调用不带 sigop 成本的GetVirtualTransactionSize。这与“vsize实际上就是 sigop 调整尺寸”的结论在代码层面完全一致。
两个新字段的官方帮助文本(mempool.cpp 中的MempoolEntryDescription)为:
vsize_adjusted:Maximum of sigop-adjusted size (-bytespersigop) and virtual transaction size as defined in BIP 141.vsize_bip141:Virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted.vsize:(DEPRECATED) Was previously erroneously described as the BIP 141 vsize, but is actually sigops-adjusted vsize.
2. testmempoolaccept
testmempoolaccept用于模拟 mempool 接受测试,其结果对象在allowed: true时新增尺寸字段,写入逻辑见 mempool.cpp:
const int64_t virtual_size = tx_result.m_vsize.value(); ... result_inner.pushKV("vsize_adjusted", virtual_size); result_inner.pushKV("vsize", virtual_size); result_inner.pushKV("vsize_bip141", GetVirtualTransactionSize(*tx));即vsize_adjusted来自接受结果MempoolAcceptResult中记录的m_vsize,与 mempool 实际策略使用的尺寸同源;vsize_bip141同样按原始 BIP 141 规则重算。结果字段的完整帮助定义见 mempool.cpp。该 RPC 还带有maxfeerate参数,用vsize_adjusted作分母校验费用上限(max_raw_tx_fee = maxfeerate.GetFee(virtual_size)),这也是“策略尺寸才是正确费率分母”的一个直接例证。
3. submitpackage
批量提交交易包(RBF 包替换等场景)的submitpackage同样在每笔交易的返回中增加vsize_adjusted与vsize_bip141,并在结果描述中将vsize标记为 DEPRECATED,给出与发布说明一致的迁移指引(mempool.cpp)。写入逻辑位于 mempool.cpp:
result_inner.pushKV("vsize_adjusted", it->second.m_vsize.value()); ... result_inner.pushKV("vsize_bip141", GetVirtualTransactionSize(*tx));4. getrawtransaction
release-notes-32800.md 的第二条指出:getrawtransaction新增vsize_adjusted,仅当交易当前位于 mempool 时返回该字段。实现位于 rawtransaction.cpp:
// Add sigop-adjusted virtual size if the transaction exists in the mempool. if (blockindex == nullptr && hash_block.IsNull() && node.mempool) { auto info = node.mempool->info(tx->GetHash()); if (info.tx) { result.pushKV("vsize_adjusted", info.vsize); } }这里的判断链值得注意:只有当请求未指定blockhash且查不到链上区块索引(blockindex == nullptr),同时 mempool 中能查到该 txid 时,才会附加该字段。也就是说,链上交易的getrawtransaction结果不含vsize_adjusted;对已确认交易而言,策略尺寸本身没有持续存在的意义。字段帮助文本为 “Sigop-adjusted virtual size in bytes, present for mempool transactions.”(rawtransaction.cpp)。
四、底层数据来源:mempool 条目如何缓存尺寸
上述 RPC 的值并非每次现场计算,而是来自 mempool 条目对象的缓存。mempool_entry.h 中:
int32_t GetTxSize() const { return GetVirtualTransactionSize(nTxWeight, sigOpCost, ::nBytesPerSigOp); } int32_t GetAdjustedWeight() const { return GetSigOpsAdjustedWeight(nTxWeight, sigOpCost, ::nBytesPerSigOp); } int32_t GetTxWeight() const { return nTxWeight; } int64_t GetSigOpCost() const { return sigOpCost; }从源码结构看,CTxMemPoolEntry在入池时就缓存了交易重量nTxWeight与脚本 sigop 成本sigOpCost,GetTxSize()每次按需套用全局::nBytesPerSigOp(对应-bytespersigop参数)完成 sigop 调整。这解释了:
- 为什么修改
-bytespersigop会改变vsize_adjusted的取值(GetTxSize()直接引用该全局值),而vsize_bip141不受影响; - 为什么
vsize_adjusted就是 mempool 驱逐、祖先/后代尺寸统计(ancestorsize/descendantsize)所使用的那个尺寸,用它做分母才能与节点策略口径一致。
五、迁移指南:从 vsize 切换到新字段
| 你的旧代码语义 | 旧字段 | 建议替换为 |
|---|---|---|
| “我按帮助文档以为它是 BIP 141 vsize”,想要原始虚拟尺寸 | vsize | vsize_bip141 |
| 我依赖的其实就是策略/费率的 sigop 调整尺寸(旧字段真实行为) | vsize | vsize_adjusted |
getrawtransaction中做 mempool 交易的策略尺寸展示 | (原本没有) | 新增的vsize_adjusted(注意可能缺失) |
实操建议:
- 读取时优先
vsize_adjusted/vsize_bip141,把vsize作为兜底(兼容旧节点); - 对
getrawtransaction的结果做存在性检查后再使用vsize_adjusted,因为链上交易没有该字段; - 若按 BIP 141 vsize 计算手续费预算,务必切换到
vsize_bip141,否则会因 sigop 放大导致估算偏差。
六、测试覆盖与验证路径
仓库的功能性测试对新字段做了断言覆盖,可用于回归验证或本地复现:
- mempool_sigoplimit.py:围绕 sigop 限制策略验证尺寸与 sigop 的关系,是观察
vsize_adjusted与vsize_bip141差异的典型用例; - mempool_accept.py:mempool 接受/拒绝路径;
- p2p_segwit.py:segwit 交易尺寸相关断言;
- rpc_packages.py:
submitpackage相关 RPC 的行为测试。
在本地运行功能测试(例如mempool_sigoplimit.py)时,可直接在节点启动参数中加入-bytespersigop=<n>观察vsize_adjusted随之变化、vsize_bip141保持不变的对照现象,从而直观确认两者的语义边界。
七、小结
本次改动(见 release-notes-32800.md)本质上是一次 RPC 契约的语义澄清:
getrawmempool、getmempoolentry、testmempoolaccept、submitpackage输出vsize_adjusted(= sigop 调整后尺寸,策略口径)与vsize_bip141(= 原始 BIP 141 虚拟尺寸),旧vsize字段保留但标记 DEPRECATED,且帮助文本改为如实描述其为 sigop 调整尺寸;getrawtransaction对 mempool 内交易追加vsize_adjusted,数据来源为 mempool 条目缓存的GetTxSize();- 计算根在 policy.cpp 的
GetSigOpsAdjustedWeight/GetVirtualTransactionSize,mempool 条目侧缓存于 mempool_entry.h。
对钱包费率估算、RBF 工具、链上分析脚本等所有消费 mempool 尺寸的下游程序而言,按本文的迁移表切换到显命名字段,即可在不改变实际行为的前提下消除对废弃字段的依赖。
【免费下载链接】bitcoinBitcoin Core integration/staging tree项目地址: https://gitcode.com/GitHub_Trending/bi/bitcoin
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考