Logo
新用户订阅 RPC,首月享 6.5 折优惠查看优惠
OnFinality Learn
网络与协议指南阅读约 12 分钟

Polkadot payment_queryInfo:将 Weight 转换为费用估算

了解如何通过 JSON-RPC 获取并解读 Polkadot 交易费用估算,为什么估算值取决于状态,以及如何排查常见故障。

TL;DR

Polkadot 交易费用由 transaction-payment pallet 计算,由基础费用、长度费用和权重费用三部分组成。权重费用源自 extrinsic 的计算权重,而该权重取决于执行情况和当前状态,并会乘以一个动态费用乘数——区块满时乘数上升,区块不满时乘数衰减。payment_queryInfo RPC 方法针对已完整构造并签名的 extrinsic 返回部分费用估算,而 payment_queryFeeDetails 则提供按组成部分的明细。由于估算值是针对当前状态和乘数计算的,它只是一种预测而非保证;客户端应在接近提交时重新估算,并注意批量交易和 tip 的处理方式。

Polkadot 费用估算与 Transaction-Payment Pallet

Polkadot 交易费用由 transaction-payment pallet 计算,它汇总三个组成部分:基础费用、与 extrinsic 编码大小成比例的长度费用,以及源自调用计算权重的权重费用。该公式记录在 Polkadot 开发者文档的计算交易费用页面中,该页面是费用组成部分及计算它们的 pallet 的权威来源。

基础费用是每个 extrinsic 的固定金额,长度费用随编码后 extrinsic 的字节长度变化,权重费用是计算权重与权重转费用换算系数的乘积,再乘以动态费用乘数。乘数根据区块饱满程度调整:当区块持续满时上升,不满时衰减,如 Polkadot 文档中关于交易费用的说明所述。

由于权重部分取决于执行情况和当前状态,总费用无法仅凭调用预测。这正是 payment_queryInfo 和 payment_queryFeeDetails 这两个 RPC 方法存在的原因:它们针对当前状态模拟 extrinsic 以返回估算值。

  • 基础费用:每个 extrinsic 固定。
  • 长度费用:与编码后 extrinsic 大小成比例。
  • 权重费用:计算权重 × 权重转费用系数 × 动态乘数。

Weight V2:二维权重及其对费用的影响

Weight V2 引入了包含 refTime(参考时间)和 proofSize(证明大小)的二维模型。refTime 表示计算时间,proofSize 表示操作所需证明的大小。这两个维度都用于计算权重费用,如 Substrate 文档中关于权重的说明以及 Polkadot 文档中关于 Weight V2 的说明所述。

调用的权重在执行之前是未知的,因为它取决于所执行的存储读取和写入。例如,一笔转账的权重可能因收款账户是否已存在而不同。因此,费用的权重部分无法在不执行的情况下仅凭调用预测。

payment_queryInfo 方法通过模拟 extrinsic 来计算权重并返回部分费用。这就是它需要已完整构造并签名的 extrinsic 的原因:签名是编码后 extrinsic 的一部分,而权重计算可能取决于签名长度和调用数据。

  • refTime:计算时间。
  • proofSize:证明大小。
  • 权重在执行时确定,而非仅凭调用确定。

payment_queryInfo:请求参数与响应字段

payment_queryInfo 方法接受单个参数:已完整构造并签名的 extrinsic,以十六进制字符串表示。根据 Polkadot.js API JSON-RPC 参考,该方法返回部分费用,并在存在时返回 extrinsic 的权重。部分费用是 extrinsic 的估算费用,不包含任何 tip。

使用未签名的调用或在签名之前调用 payment_queryInfo 会失败,因为 extrinsic 十六进制必须包含签名。该方法会模拟 extrinsic 被包含在区块中的情况,而签名是该模拟的一部分。如果传入未签名的 extrinsic,该方法将返回错误,通常表明该 extrinsic 无效或无法解码。

响应包含以字符串表示的部分费用,该字符串表示以最小单位计量的金额(例如 DOT 的 Planck)。它还可能包含权重,这对于理解权重部分很有用。不过,部分费用已经包含权重费用,因此无需单独计算。

  • 参数:已签名的 extrinsic 十六进制。
  • 返回:部分费用(字符串),以及可选的权重。
  • 如果 extrinsic 未签名或格式错误则失败。

payment_queryFeeDetails:按组成部分的费用明细

payment_queryFeeDetails 方法提供比 payment_queryInfo 更丰富的响应:它返回按组成部分拆分的费用:base、len、adjustedWeight 和 tip。这使客户端能够确切看到成本来自何处,如 Polkadot.js API JSON-RPC 参考所述。

adjustedWeight 部分是应用动态费用乘数后的权重费用。tip 是发送方为优先处理交易而包含的可选额外金额。base 和 len 部分分别是固定费用和长度费用。

当你需要了解费用构成或希望向用户展示明细时,建议使用 payment_queryFeeDetails。它接受与 payment_queryInfo 相同的参数:已签名的 extrinsic 十六进制。

  • 返回:base、len、adjustedWeight、tip。
  • adjustedWeight 包含动态乘数。
  • 参数与 payment_queryInfo 相同。

取决于状态的估算值与动态费用乘数

权重费用会乘以一个根据区块饱满程度调整的动态费用乘数。当区块满时,乘数上升,权重费用增加;当区块不满时,乘数衰减。该机制在 Polkadot 文档中关于交易费用的说明中有所描述。

由于乘数随时间变化,同一 extrinsic 在不同时间得到的两个估算值会不同。在乘数较低时获得的估算值,如果乘数在交易被包含之前上升,可能低于实际收取的费用。反之,如果乘数衰减,实际费用可能低于估算值。

乘数存储在 transaction-payment pallet 的存储中,可以使用状态查询在特定区块读取。这使客户端能够判断所报费用处于正常还是偏高的乘数水平。有关在特定区块读取存储的更多信息,请参阅使用 state_queryStorageAt 读取 Polkadot 区块状态

  • 区块满时乘数上升,不满时衰减。
  • 估算值随查询时的乘数而变化。
  • 从存储读取乘数以评估费用水平。
# payment_queryInfo takes a signed extrinsic hex; read the multiplier for the same block.
curl -s -X POST "$POLKADOT_RPC" -H 'Content-Type: application/json' --data '{"jsonrpc":"2.0","id":1,"method":"payment_queryInfo","params":["0x<signed-extrinsic-hex>"]}'

# The transaction-payment fee multiplier raises the weight component when blocks are full.
curl -s -X POST "$POLKADOT_RPC" -H 'Content-Type: application/json' --data '{"jsonrpc":"2.0","id":2,"method":"state_getStorage","params":["0x<twox64_concat(\"TransactionPayment\", \"NextFeeMultiplier\")>"]}'

从 Transaction-Payment 存储读取费用乘数

费用乘数存储在 transaction-payment pallet 的 NextFeeMultiplier 存储项下。你可以使用 state_getStorage RPC 方法或通过 polkadot.js API 在特定区块查询它。该值是一个表示乘数的定点数。

要读取乘数,你需要 NextFeeMultiplier 的存储键。你可以使用 Substrate state_getMetadata 与运行时版本指南从元数据中获取它。或者,polkadot.js API 提供了查询它的便捷方式。

了解乘数有助于解读估算值:如果乘数高,权重费用偏高,估算值可能高于平常。如果乘数低,估算值则更接近基础费用和长度费用。

  • 存储项:NextFeeMultiplier。
  • 通过 state_getStorage 或 polkadot.js API 查询。
  • 乘数高表示权重费用偏高。

可运行的 Node.js 示例:使用 @polkadot/api 估算费用

以下 Node.js 脚本使用 @polkadot/api 连接到 Polkadot RPC 端点,构造一笔转账 extrinsic,使用 payment_queryInfo 估算其费用,读取费用乘数,并将该估算值与同一调用的批量交易进行比较。它假定你拥有一个有资金的账户和一个有效的端点。

该脚本首先构造 extrinsic、对其进行签名,然后使用已签名的 extrinsic 十六进制调用 payment_queryInfo。它还会查询 NextFeeMultiplier 存储项。最后,它构造同一转账的批量交易并估算其费用,以表明批量费用并非各调用估算值的简单相加。

const { ApiPromise, WsProvider, Keyring } = require('@polkadot/api');

async function main() {
  const provider = new WsProvider('wss://rpc.polkadot.io');
  const api = await ApiPromise.create({ provider });

  const keyring = new Keyring({ type: 'sr25519' });
  const alice = keyring.addFromUri('//Alice');

  const transfer = api.tx.balances.transferKeepAlive('14E5nqKAp3oAJcmzgZhUD2RcptBeUBScxKHgJKU4HPNcKVf3', 1000000000);
  const signed = await transfer.signAsync(alice);
  const hex = signed.toHex();

  const info = await api.rpc.payment.queryInfo(hex);
  console.log('Partial fee:', info.partialFee.toString());
  console.log('Weight:', info.weight.toString());

  const multiplier = await api.query.transactionPayment.nextFeeMultiplier();
  console.log('Next fee multiplier:', multiplier.toString());

  const batch = api.tx.utility.batchAll([transfer, transfer]);
  const signedBatch = await batch.signAsync(alice);
  const batchInfo = await api.rpc.payment.queryInfo(signedBatch.toHex());
  console.log('Batch partial fee:', batchInfo.partialFee.toString());

  await api.disconnect();
}

main().catch(console.error);

结果表:针对你自己的端点测量估算值

要针对你自己的端点验证费用估算,请用你自己测试的数据填写下表。使用一致的 extrinsic(例如一笔转账),记录 payment_queryInfo 返回的估算值、权重、估算时的乘数,以及被包含后的实际费用。这将帮助你了解在你的环境中估算值与实际费用的对比情况。

在不同区块高度多次运行该测试,以观察乘数的影响。请注意,实际费用是从发送方账户扣除的真实费用,你可以在交易支付事件中找到它,或通过比较被包含前后的余额得出。

  • 返回的估算值(部分费用):
  • 权重:
  • 估算时的乘数:
  • 被包含后的实际费用:
  • 差值(实际 - 估算):

故障模式与排查

常见故障模式包括:未签名或格式错误的 extrinsic 导致“payment_queryInfo 返回错误”;由于乘数上升导致估算值低于实际收取的费用;批量交易的费用并非各调用估算值之和;以及端点未暴露 payment 命名空间。后者因提供商而异,因此请查阅你的提供商文档。

如果未签名的 extrinsic 返回错误,请确保在调用 payment_queryInfo 之前对 extrinsic 进行签名。如果估算值低于实际费用,请在接近提交时重新估算,并考虑添加 tip 以优先被包含。对于批量交易,费用是针对整个批量作为单个 extrinsic 计算的,因此它不是各个估算值之和;请对批量 extrinsic 本身使用 payment_queryInfo。

如果你的端点不支持 payment 命名空间,你可能需要切换到支持的提供商。OnFinality 的 Polkadot RPC 端点(RPC Assistant)可以帮助你找到合适的端点。有关处理 dispatch 错误的更多信息,请参阅解码 Polkadot extrinsic dispatch 错误

  • 未签名的 extrinsic:调用前先签名。
  • 乘数上升:在接近提交时重新估算。
  • 批量费用:估算批量 extrinsic,而非单个调用。
  • 端点支持:因提供商而异。

费用估算的局限性与权衡

费用估算值是针对当前状态和当前费用乘数的预测。它并不保证实际将被收取的费用。实际费用取决于交易被包含所在区块的状态,该状态可能与估算时的状态不同。

Tip 处理:payment_queryInfo 返回的部分费用不包含 tip。如果你包含 tip,总费用会更高。tip 会加到费用上,且不受乘数影响。

由于估算值取决于状态,建议在接近提交时重新估算,尤其是在网络活动高峰期。有关 Polkadot RPC 方法的更广泛概述,请参阅 OnFinality Learn 中心Polkadot RPC 端点(RPC Assistant)

  • 估算值不是保证。
  • 部分费用不包含 tip。
  • 在接近提交时重新估算。

后续步骤:将费用估算集成到你的应用中

要将费用估算集成到你的应用中,请在提交交易之前使用 payment_queryInfo 或 payment_queryFeeDetails 获取估算值。向用户显示估算值,但要明确说明这只是估算,可能会变化。考虑读取费用乘数,以提供费用当前是否偏高的背景信息。

对于生产环境使用,请确保你的 RPC 端点支持 payment 命名空间。OnFinality 提供可靠的 Polkadot RPC 端点和可用于费用估算的 API 服务。有关定价详情,请参阅 RPC 定价

要加深对 Polkadot RPC 的理解,请探索相关指南,例如在特定区块读取 Polkadot extrinsic 和事件Polkadot GRANDPA 最终性与 justification

  • 使用 payment_queryInfo 或 payment_queryFeeDetails。
  • 读取乘数以获取背景信息。
  • 选择支持 payment 命名空间的端点。

永远不用担心基础设施

OnFinality 消除了 DevOps 的繁重工作,让您能够更聪明、更快地构建。

开始