当 Polkadot/Substrate 外部交易在 RPC 层失败时,JSON-RPC 错误 '1010: Invalid Transaction' 是一个包装,其 data 字段包含交易池有效性标签(例如 Payment、Stale、TemporarilyBanned),揭示真正原因。对于通过交易池但在执行期间失败的外部交易,失败通过 author_submitAndWatchExtrinsic 事件和运行时 DispatchError 显现,您可以使用链的元数据对其进行解码。本文解释了该机制,提供了可运行的 @polkadot/api 脚本以捕获和解码这些错误,并给出了标签到修复的清单。
直接回答:1010 无效交易真正意味着什么
当您通过 author_submitExtrinsic 或 author_submitAndWatchExtrinsic 向 Polkadot 或 Substrate 节点提交外部交易时,节点的交易池会在接受交易之前执行有效性检查。如果该检查失败,RPC 返回一个 JSON-RPC 错误,代码为 1010,消息为 Invalid Transaction。实际原因编码在 data 字段中,作为诸如 Payment、Stale 或 TemporarilyBanned 的标签。此标签不是随机字符串;它对应于 Substrate 交易池框架中定义的 InvalidTransaction 枚举变体。要修复您的提交,您必须解码该标签并解决根本问题——无论是资金不足、nonce 错误还是发送者被禁止。对于通过交易池但在执行期间失败的外部交易,失败稍后作为交易事件中的 DispatchError 出现,您可以使用链的运行时元数据对其进行解码。
本指南是 OnFinality Learn 中心 的一部分,专注于 Polkadot 生态系统。如果您是 Polkadot RPC 端点的新手,请先参阅 Polkadot RPC 指南。对于超时或速率限制等传输层问题,请参阅我们的 超时、延迟 和 速率限制 文章。
外部交易提交在底层如何工作
向 Polkadot 节点提交外部交易是一个两阶段过程。首先,交易池根据当前状态和交易池规则验证外部交易。这就是 1010 Invalid Transaction 错误的来源。交易池检查诸如 nonce(是否是下一个预期的)、交易的寿命(是否不太旧)以及发送者支付费用的能力等。如果任何检查失败,交易池返回一个 InvalidTransaction 变体,RPC 层将其序列化到错误的 data 字段中。
其次,如果外部交易通过交易池,它会被传播并包含在一个区块中。执行发生在区块生产期间。如果外部交易的调用在运行时失败——例如,由于错误的 origin 或特定于 pallet 的错误——交易不会被回滚;相反,它被包含在区块中但标记为失败。失败通过 system.ExtrinsicFailed 事件报告,其中包含 DispatchError。要看到这一点,您必须使用 author_submitAndWatchExtrinsic,它会发出 transactionStatus 更新,包括池拒绝的 Invalid 和 Drop,以及成功包含的 Finalized 和区块哈希。DispatchError 不是 RPC 错误的一部分;您必须查询区块事件来解码它。
此机制在 Substrate 交易池文档 和 FRAME 调度文档 中有记录。Polkadot 开发者文档也涵盖了 外部交易和交易。
解码 1010 错误数据标签
1010 Invalid Transaction 错误的 data 字段是一个字符串,与 Substrate 交易池中的 InvalidTransaction 变体之一匹配。您会遇到的最常见的变体有:
Payment– 发送者无法支付交易费用(例如,余额不足或费用计算错误)。
Stale– nonce 太低(已使用)或交易太旧。
Future– nonce 高于当前账户 nonce(尚无效)。
TemporarilyBanned– 发送者被暂时禁止进入交易池,通常是由于提交了太多无效交易。
BadProof– 签名或签名负载无效。
AncientBirthBlock– 交易的era(死亡率)太旧;出生区块超出BlockHashCount。
ExhaustsResources– 交易池已满或交易将超过区块权重限制。
Custom(u8)– 链特定的有效性错误,通常来自自定义交易扩展。
要查看确切的标签,您必须在客户端代码中捕获错误对象。标签位于 error.data(或在某些库中为 error.data.toString())。不要仅依赖消息,因为它是通用的。
下表将每个标签映射到其典型原因和修复方法。这基于 Substrate 源代码 和社区经验。
- Payment – 原因:余额不足以支付费用或与费用相关的问题。修复:确保账户有足够的自由余额来支付费用加上任何存在性存款;通过
api.tx.balances.transfer.estimate检查费用。 - Stale – 原因:nonce 太低或交易太旧。修复:使用来自
api.query.system.account的当前 nonce,并设置适当的era(例如,使用era: 64的api.tx.balances.transfer)。 - Future – 原因:nonce 太高。修复:等待之前的交易被处理或设置正确的 nonce。
- TemporarilyBanned – 原因:来自同一发送者的重复无效提交。修复:等待禁令过期(通常几分钟)并修复根本问题。
- BadProof – 原因:签名或签名负载无效。修复:确保您使用正确的账户签名,并且负载与链的签名扩展匹配。
- AncientBirthBlock – 原因:交易的
era太长或出生区块太旧。修复:使用较短的era或让 API 自动设置。 - ExhaustsResources – 原因:交易池已满或交易将超过区块限制。修复:稍后重试或降低交易的复杂性。
- Custom(u8) – 原因:链特定的有效性错误。修复:查阅链的文档或源代码以了解自定义代码的含义。
可运行示例:捕获和解码 1010 错误
以下 Node.js 脚本使用 @polkadot/api 连接到用户提供的 WebSocket 端点,构建并签名一个简单的转账外部交易,然后提交。它捕获 JSON-RPC 错误并打印完整的错误对象,包括 data 字段。它还演示了如何使用 author_submitAndWatchExtrinsic 在交易被接受时捕获执行事件。
先决条件:Node.js 18+,@polkadot/api 版本 10.9.1(截至 2026-09-05)。使用 npm install @polkadot/api 安装。将 WS_URL 替换为您的端点(例如,Polkadot 使用 wss://rpc.polkadot.io,Asset Hub 使用 wss://statemint-rpc.polkadot.io)。
重要:脚本构建了一个向虚拟地址转账 0 DOT 的交易。这是安全的,因为金额为零,但如果发送者没有资金支付费用,它可能仍然会失败。为了避免花费资金,您可以使用只读调用,如 api.tx.balances.transfer 且金额为零,但请注意某些链会拒绝零值转账。为了安全演示,您也可以使用 api.tx.system.remark 并附带一个小备注,这只需要费用。脚本旨在暴露错误,而不是执行真实的转账。
const { ApiPromise, WsProvider, Keyring } = require('@polkadot/api');
// Replace with your endpoint
const WS_URL = 'wss://rpc.polkadot.io';
async function main() {
const provider = new WsProvider(WS_URL);
const api = await ApiPromise.create({ provider });
// Create a keyring from a dev seed (for testing only)
const keyring = new Keyring({ type: 'sr25519' });
const alice = keyring.addFromUri('//Alice');
// Build a transfer of 0 DOT to a dummy address
const dummy = '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty'; // Alice's address for demo
const tx = api.tx.balances.transfer(dummy, 0);
// Sign the transaction
const signed = await tx.signAsync(alice);
// Submit and watch
try {
const unsub = await signed.send(({ status, events, dispatchError }) => {
if (status.isInBlock || status.isFinalized) {
console.log('Transaction included in block:', status.asInBlock.toHex());
if (dispatchError) {
console.log('Dispatch error:', decodeDispatchError(api, dispatchError));
}
events.forEach(({ event }) => {
if (api.events.system.ExtrinsicFailed.is(event)) {
console.log('Extrinsic failed:', event.data.toString());
}
});
unsub();
}
});
} catch (error) {
// This is where the 1010 error appears
console.error('Submission error:', JSON.stringify(error, null, 2));
if (error.data) {
console.log('Error data (tag):', error.data.toString());
}
}
await api.disconnect();
}
function decodeDispatchError(api, dispatchError) {
if (dispatchError.isModule) {
const { index, error } = dispatchError.asModule;
const meta = api.registry.findMetaError({ index, error });
return `${meta.section}.${meta.name}: ${meta.docs.join(' ')}`;
} else {
return dispatchError.toString();
}
}
main().catch(console.error);预期输出和结果表
当您使用一个没有资金的账户运行脚本时,您可能会看到类似这样的错误(实际输出因链和账户状态而异):
如果交易被接受,您将看到区块哈希,可能还有调度错误。用您自己的结果填写下表,以记录目标链上的行为。
- 链/端点:例如,Polkadot、Asset Hub 或自定义平行链。
- 账户余额:签名账户的自由余额。
- 使用的 nonce:您设置的 nonce(或自动填充的)。
- 错误代码:例如,1010 或 0。
- 错误数据标签:例如,Payment、Stale 等。
- 调度错误(如果有):例如,
Module { index: 5, error: 3 }解码为balances.InsufficientBalance。 - 应用的修复:您为解决该问题所做的更改。
{
"code": 1010,
"message": "Invalid Transaction",
"data": "Payment"
}
解码执行失败中的调度错误
当外部交易通过交易池但在执行期间失败时,失败不会作为 RPC 错误返回。相反,交易被包含在区块中,并发出 system.ExtrinsicFailed 事件。该事件包含一个 DispatchError,它可以是以下几种变体之一:
Module { index, error }– 特定于 pallet 的错误。index指的是运行时中 pallet 的索引,error是该 pallet 内的错误索引。您必须使用运行时元数据来解码它们。
BadOrigin– 来源(发送者)不被允许调用此函数。
Token– 令牌错误,如NoFunds或BelowMinimum。
Arithmetic– 算术溢出或下溢。
Other– 其他错误的包罗万象。
要解码 Module 错误,您需要运行时元数据。@polkadot/api 提供了一个辅助函数:api.registry.findMetaError({ index, error })。它返回一个包含 section、name 和 docs 的对象。例如,如果您得到 Module { index: 5, error: 3 },它可能解码为 balances.InsufficientBalance。
上面的脚本包含一个 decodeDispatchError 函数,可以自动执行此操作。请注意,pallet 索引可能因链而异,因此始终使用您正在查询的特定链的元数据。
有关调度错误的更多信息,请参阅 FRAME 调度文档 和 Polkadot 开发者文档中的交易部分。
常见错误及如何避免
许多外部交易提交失败源于几个反复出现的错误。以下是诊断它们的清单:
- 不正确的 nonce:如果您从同一账户提交多个交易,您必须手动递增 nonce 或使用
api.derive.balances.account获取当前 nonce。两次使用相同的 nonce 将导致Stale错误。
- 资金不足以支付费用:即使转账金额为零,您也需要足够的余额来支付交易费用。在提交前使用
api.tx.balances.transfer.estimate检查费用。
- 错误的 era(死亡率):如果您设置了自定义的
era且太长,交易可能会被拒绝为AncientBirthBlock。使用api.tx.balances.transfer而不指定 era,让 API 设置安全的默认值。
- 使用过时的端点:如果您连接到一个落后的节点,您的交易可能会被拒绝为
Stale。确保您连接到一个已同步的节点。OnFinality 提供可靠的端点;有关详细信息,请参阅我们的 Polkadot 网络页面。
- 不处理
data字段:许多开发者只检查错误消息而错过了标签。始终记录完整的错误对象。
- 假设所有链都相同:Pallet 索引和错误代码在 Polkadot 和平行链之间有所不同。始终使用特定链的元数据。
对于超时或速率限制等传输层问题,请参阅我们的 WebSocket 指南 和 速率限制文章。
局限性和权衡
这里描述的方法依赖于节点的交易池和运行时元数据。有一些局限性:
- 节点特定行为:交易池的有效性检查可能因节点版本和链配置而略有不同。
1010错误是标准的,但确切的标签在自定义平行链上可能有所不同。
- 调度错误仅在包含后可见:如果您使用
author_submitExtrinsic(而不是 watch),您将看不到执行失败。您必须使用author_submitAndWatchExtrinsic并监听事件。
- 元数据更改:运行时升级可以更改 pallet 索引和错误代码。始终从链中获取最新的元数据。
- 提供商差异:一些 RPC 提供商可能以不同的方式包装错误或添加额外的字段。OnFinality 的端点遵循标准的 Substrate RPC,但如果您使用第三方提供商,请测试错误格式。有关定价和服务详细信息,请参阅我们的 RPC 定价 和 API 服务 页面。
本指南不能替代阅读链的文档。有关 Polkadot 特定的详细信息,请参阅 官方 Polkadot 文档。
后续步骤和进一步阅读
既然您能够解码 1010 错误和调度失败,您可以更有效地调试您的外部交易。要进一步深入:
- 探索 Polkadot RPC 指南 以获取完整的 RPC 方法列表。
- 了解 Polkadot RPC 超时 和 延迟 以优化您的连接。
- 理解 速率限制和 429 以避免被限制。
- 对于 WebSocket 特定问题,请参阅 Polkadot WebSocket RPC 指南。
- 如果您在平行链上构建,请查阅链自己的文档以了解自定义交易扩展和错误。
如果您需要可靠的 RPC 端点,OnFinality 提供公共和私有端点;有关详细信息,请参阅我们的 网络页面。对于生产使用,请考虑我们的 API 服务 以获得专门支持。