system_dryRun 是一种 Substrate JSON-RPC 方法,它针对节点当前的存储覆盖层应用外部交易或 XCM 消息,并返回 Ok(消耗的权重) 或 Err(带有 post-info 的 DispatchError),而不会写入链上状态。这是一种安全的提交前检查:它告诉你调用是否会成功以及消耗多少权重,但它不强制执行交易池有效性、不消耗 nonce、不支付费用,也不发出事件。由于干运行是在某一个区块上评估的,而真实提交是在之后的区块执行,因此成功的干运行是成功提交的必要条件,但不是充分条件。本指南展示如何使用 @polkadot/api 调用 system_dryRun、解析文本编码的 Result、将返回的权重与 payment_queryInfo 费用估算进行比较,并将输出解读为允许/警告/拒绝决策。
system_dryRun 针对当前状态的作用
system_dryRun 是一种 Substrate JSON-RPC 方法,它针对节点当前的存储覆盖层应用外部交易或 XCM 消息,并返回结果而不提交任何状态更改。节点会像该调用被包含在区块中一样执行它,测量消耗的权重,然后丢弃覆盖层。不会向链上状态写入任何内容,不会发出事件,也不会消耗 nonce。polkadot.js 的 Substrate JSON-RPC 参考文档在 polkadot.js.org/docs/substrate/rpc 中记录了该方法及其变体。
返回值是文本编码的 Result。成功时是 Ok(weight),其中 weight 是调用消耗的权重。失败时是 Err(带有 post-info 的 DispatchError),它携带调度错误以及错误发生前消耗的权重。客户端必须解析这种文本编码,而不是将响应视为普通 JSON 对象。由于调用是针对节点当前的存储覆盖层应用的,结果反映的是节点当前正在导入的区块或你通过可选 at 参数指定的区块的状态。
这使得 system_dryRun 成为一种安全的方式,可以在你花费 nonce 或支付费用之前了解调用是否会成功以及消耗多少权重。它是读取外部交易池的提交前对应方法,相关内容在 Polkadot 外部交易池与待处理交易 中介绍。
- 针对当前存储覆盖层应用外部交易或 XCM 消息。
- 成功时返回 Ok(weight),失败时返回 Err(带有 post-info 的 DispatchError)。
- 不写入链上状态、不发出事件、不消耗 nonce、不支付费用。
- 在某个区块上评估;真实提交在之后的区块执行。
参数形式与可选的 at 区块哈希
system_dryRun 的参数根据节点版本有两种形式。一种形式是传入编码后的外部交易(已签名或未签名的外部交易字节)。另一种形式是传入十六进制编码的调用(不包含外部交易封装的调用字节)。polkadot.js 的 Substrate JSON-RPC 参考文档记录了这两种变体。由于编码在不同客户端版本间存在差异,在构建请求之前应确认目标节点期望哪种形式。可选的 at 参数允许你将干运行固定到特定的区块哈希;如果省略,节点使用其当前头部。
当你将 at 固定到某个区块哈希时,干运行会针对该区块的状态进行评估。这对于可复现性很有用:你可以针对同一区块重新运行相同的模拟并比较结果。当你想要针对已知良好的状态而不是移动的头部检查调用时,这也很有用。代价是,在旧区块上的干运行可能无法反映真实提交将要执行的状态。
如果你不确定使用哪个区块哈希,可以使用 chain_getHeader 和 chain_getBlockHash 读取当前头部。在特定区块读取区块数据的方法在 在特定区块读取 Polkadot 区块数据 中介绍。
- 形式 1:编码后的外部交易字节。
- 形式 2:十六进制编码的调用字节。
- 可选的 at 参数将干运行固定到特定区块哈希。
- 编码在不同客户端版本间存在差异;构建请求前请确认。
dryRun 与提交外部交易的区别
提交外部交易并等待事件与干运行是不同的操作。真实提交会进入交易池,根据池规则进行验证,消耗 nonce,支付费用,并在包含在区块中时发出事件。干运行不做这些事情。它不强制执行交易池有效性、不消耗 nonce、不支付费用,也不发出事件。polkadot.js 的 Substrate JSON-RPC 参考文档记录了该方法的返回结构,Polkadot 开发者文档 docs.polkadot.com 涵盖了外部交易、权重和调度语义。
这意味着干运行返回的 Ok 是成功真实提交的必要条件,但不是充分条件。调用可能通过干运行,但在提交时仍然失败,因为交易池拒绝它、nonce 过期、无法支付费用,或者状态在干运行和真实执行之间发生了变化。干运行告诉你的是调度结果和权重,而不是交易池准入或费用支付。
实际后果是,你应该将干运行视为提交决策的一个输入,而不是保证。在签名之前,将其与费用估算和 nonce 检查配对使用。费用估算工作流在 Polkadot payment_queryInfo 与费用估算 中介绍。
- 干运行:无交易池验证、无 nonce、无费用、无事件。
- 真实提交:交易池验证、nonce 消耗、费用支付、事件。
- 干运行的 Ok 是成功提交的必要条件,但不是充分条件。
- 状态可能在干运行和真实执行之间发生变化。
使用返回的权重对费用估算进行合理性检查
成功干运行返回的权重是调用将消耗的权重。你可以用它来对 payment_queryInfo 的费用估算进行合理性检查。如果费用估算暗示的权重与干运行权重相差甚远,说明存在不一致:估算可能基于不同的调用、不同的区块或不同的权重限制。payment_queryInfo 方法返回基于权重和长度推导出的费用估算,推导过程在 Polkadot payment_queryInfo 与费用估算 中介绍。
你还可以使用干运行权重在签名前设置权重限制。如果权重限制设置得太低,即使干运行成功,调用也会因超重错误而失败。如果设置得太高,你可能会多付费用或触及区块限制。干运行为你提供了一个实测权重来锚定限制。请注意,干运行返回的权重是在你模拟的区块上消耗的权重;如果状态不同,真实执行可能消耗不同的量。
由于干运行不支付费用,它返回的权重不是费用。它是费用计算的输入。将其视为测量值,而不是收费。
- 将干运行权重与 payment_queryInfo 暗示的权重进行比较。
- 使用干运行权重在签名前设置权重限制。
- 权重限制过低会导致超重失败,尽管干运行成功。
- 干运行权重是测量值,不是费用。
XCM 与合约调用模拟的差异
system_dryRun 有针对不同负载类型的变体。一个变体模拟 XCM 消息,另一个模拟合约调用。差异很重要,因为负载编码和错误面不同。XCM 干运行返回 XCM 特定的错误,而合约调用干运行返回合约特定的错误。polkadot.js 的 Substrate JSON-RPC 参考文档记录了这些变体。你应该将变体与你打算提交的负载匹配。
对于 XCM,干运行针对当前状态应用消息,并返回消耗的权重或 XCM 错误。对于合约调用,干运行针对合约的存储应用调用,并返回消耗的权重或合约错误。在这两种情况下,干运行都不会提交状态。实际好处是相同的:你在提交之前了解负载是否会成功以及消耗多少权重。
如果你使用的链暴露了这些变体,请检查节点的元数据或 polkadot.js 参考文档以获取确切的方法名称。编码在不同客户端版本间存在差异,因此在一个节点上有效的负载在另一个节点上可能需要不同的编码。
- XCM 变体返回 XCM 特定的错误和权重。
- 合约调用变体返回合约特定的错误和权重。
- 将变体与你打算提交的负载匹配。
- 编码在不同客户端版本间存在差异。
在花费 nonce 之前解码 DispatchError
当干运行失败时,它返回 Err(带有 post-info 的 DispatchError)。DispatchError 告诉你调用为什么会失败。在提交之前解码它,可以让你在不花费 nonce 或支付费用的情况下修复调用。错误处理工作流在 解码 Polkadot 外部交易调度错误 中介绍。
错误中的 post-info 携带错误发生前消耗的权重。这对于了解调用在失败前执行了多远很有用。它不是费用,因为干运行不支付费用。它是对错误发生前所做工作的测量。
常见的 DispatchError 变体包括 BadOrigin、模块错误和其他运行时特定的错误。确切的集合取决于运行时。你应该针对目标链的运行时元数据解码错误。如果错误是模块错误,模块索引和错误索引标识了具体的失败。
- Err 携带 DispatchError 和 post-info 权重。
- 在提交前解码错误,以避免花费 nonce。
- post-info 是错误前消耗的权重,不是费用。
- 针对目标链的运行时元数据解码。
可运行的 Node.js 示例:干运行与费用比较
以下 Node.js 示例连接到 Polkadot WebSocket 端点,使用 @polkadot/api 编码调用,在当前头部调用 system_dryRun,解析得到的权重,并将其与 payment_queryInfo 费用估算进行比较。将端点替换为你自己的提供商端点。该示例使用简单的 balances 转账调用;请根据你的用例调整调用。
该示例假设你已安装 @polkadot/api。它使用 api.rpc.system.dryRun 方法,该方法封装了 system_dryRun RPC。结果是文本编码的 Result,因此示例使用 api 的 registry 解析它。示例还调用 payment_queryInfo 获取费用估算,并打印两者以进行比较。
const { ApiPromise, WsProvider } = require('@polkadot/api');
async function main() {
const provider = new WsProvider('wss://your-polkadot-endpoint');
const api = await ApiPromise.create({ provider });
// Build a call: transfer 1 DOT to a recipient.
const recipient = '15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5';
const amount = '10000000000'; // 1 DOT in plancks
const call = api.tx.balances.transferKeepAlive(recipient, amount);
// Get the current head block hash.
const head = await api.rpc.chain.getHeader();
const at = head.hash.toHex();
// Dry run the call at the current head.
const dryRunResult = await api.rpc.system.dryRun(call.toHex(), at);
console.log('dryRun raw:', dryRunResult.toString());
// Parse the text-encoded Result.
const parsed = api.registry.createType('Result<Weight, DispatchError>', dryRunResult);
if (parsed.isOk) {
const weight = parsed.asOk;
console.log('dryRun weight:', weight.toString());
} else {
const err = parsed.asErr;
console.log('dryRun error:', err.toString());
}
// Get a fee estimate for the same call.
const info = await api.rpc.payment.queryInfo(call.toHex(), at);
console.log('payment_queryInfo:', info.toString());
await api.disconnect();
}
main().catch(console.error);可运行的 curl 示例:原始 system_dryRun 请求
如果你更喜欢直接调用 RPC,以下 curl 示例发送原始 system_dryRun 请求。将端点和编码后的调用替换为你自己的值。call 字段是十六进制编码的调用。at 字段是可选的;如果省略,节点使用其当前头部。
响应是一个 JSON-RPC 2.0 对象,其结果是文本编码的 Result。你必须解析结果字符串以提取权重或 DispatchError。JSON-RPC 2.0 规范定义了请求和响应封装,而 Ethereum JSON-RPC 规范是不同生态系统中相同封装语义的有用参考。
curl -sS -H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "system_dryRun",
"params": [
"0x...encoded-call...",
"0x...block-hash..."
]
}' \
https://your-polkadot-endpoint允许、警告或拒绝决策的可复现检查清单
使用以下检查清单将干运行转化为提交决策。该检查清单是可复现的:针对你自己的端点运行它,并将结果记录在表格中。表格列应包括调用、区块哈希、干运行结果、干运行权重、payment_queryInfo 估算和决策。用你自己的测量值填写表格;不要依赖本文中的数字。
决策类别是允许、警告和拒绝。允许意味着干运行成功且权重在你的限制范围内。警告意味着干运行成功但权重接近你的限制,或者费用估算不一致。拒绝意味着干运行因 DispatchError 失败,或者权重超过你的限制,或者无法支付费用估算。
记录每次运行的区块哈希,以便复现。如果你针对不同的区块重新运行,结果可能会不同。该检查清单是一种方法,而不是保证。
- 在固定的区块哈希上运行 system_dryRun 并记录结果。
- 解析 Ok(weight) 或 Err(带有 post-info 的 DispatchError)。
- 将干运行权重与你的权重限制进行比较。
- 将干运行权重与 payment_queryInfo 估算进行比较。
- 根据比较结果决定允许、警告或拒绝。
- 在表格中记录区块哈希、调用、结果、权重、估算和决策。
干运行模拟的局限性与权衡
最重要的局限是干运行是针对某一个区块的状态进行评估的,而真实提交是在之后的区块执行。依赖状态的调用在成功干运行后仍可能失败。例如,如果发送者的余额在干运行和真实执行之间发生变化,转账可能会失败。如果提案状态发生变化,治理调用可能会失败。干运行是快照,不是预测。
一些节点限制或禁用 system_dryRun。这是因节点而异的已记录行为。节点运营者可能出于性能、安全或策略原因禁用该方法。如果该方法不可用,你就无法将其用作提交前检查。在依赖它之前,应确认目标端点的可用性。
参数编码在不同客户端版本间存在差异。在一个节点上有效的负载在另一个节点上可能需要不同的编码。在构建请求之前应确认期望的形式。最后,干运行不模拟交易池优先级或替换费用行为。通过干运行的调用仍可能被从池中丢弃或被更高费用的交易替换。关于池行为,请参阅 Polkadot 外部交易池与待处理交易。
- 干运行在某个区块上评估;真实执行发生在之后。
- 依赖状态的调用在成功干运行后仍可能失败。
- 一些节点限制或禁用 system_dryRun;可用性因节点而异。
- 参数编码在不同客户端版本间存在差异。
- 干运行不模拟池优先级或替换费用。
排查常见的 system_dryRun 失败
如果 system_dryRun 返回方法未找到错误,节点可能未暴露该方法,或者以不同的名称暴露。检查节点的元数据和 polkadot.js 的 Substrate JSON-RPC 参考文档。如果该方法被禁用,你就无法使用它;考虑使用不同的端点或不同的提交前检查。
如果干运行返回 Err(DispatchError),请针对运行时元数据解码错误。常见原因包括 BadOrigin、余额不足和模块特定的错误。解码工作流在 解码 Polkadot 外部交易调度错误 中介绍。如果错误是模块错误,模块索引和错误索引标识了具体的失败。
如果干运行成功但真实提交失败,状态很可能在干运行和提交之间发生了变化。在当前头部重新运行干运行并比较。如果失败是池拒绝,检查 nonce 和费用。如果失败是超重错误,根据干运行权重增加权重限制。如果失败是 nonce 过期,刷新 nonce 并重新签名。
- 方法未找到:检查元数据和参考文档;方法可能被禁用。
- DispatchError:针对运行时元数据解码。
- 成功干运行后真实提交失败:状态已变化;在当前头部重新运行。
- 超重错误:根据干运行权重增加权重限制。
- nonce 过期:刷新并重新签名。
提交前模拟的后续步骤
要将 system_dryRun 付诸实践,首先确认你的目标端点暴露了该方法。你可以使用 Polkadot RPC 指南(RPC Assistant) 来探索可用的方法。然后构建一个小脚本,在当前头部干运行你的调用并打印权重或 DispatchError。将权重与 payment_queryInfo 估算进行比较,并将结果记录在表格中。
对于托管端点,请参阅 Polkadot 和 API 服务。关于定价,请参阅 RPC 定价。更多指南,请参阅 OnFinality Learn 中心。
在构建提交前工作流时,将干运行与费用估算和 nonce 检查配对使用。干运行告诉你调度结果和权重;费用估算告诉你成本;nonce 检查告诉你池准入。它们一起提供了比任何单一检查更完整的画面。
- 确认端点暴露了 system_dryRun。
- 在当前头部干运行你的调用并记录结果。
- 将权重与 payment_queryInfo 进行比较并记录比较结果。
- 将干运行与费用估算和 nonce 检查配对使用。