Logo
新用户订阅 RPC,首月享 6.5 折优惠查看优惠
OnFinality Learn
RPC 故障排查阅读约 12 分钟

解码 Polkadot 外部交易提交错误:1010 无效交易与调度失败

学习解码 Polkadot RPC 1010 无效交易标签和调度错误,附带可运行的 @polkadot/api 脚本和修复清单。

TL;DR

当 Polkadot/Substrate 外部交易在 RPC 层失败时,JSON-RPC 错误 '1010: Invalid Transaction' 是一个包装,其 data 字段包含交易池有效性标签(例如 Payment、Stale、TemporarilyBanned),揭示真正原因。对于通过交易池但在执行期间失败的外部交易,失败通过 author_submitAndWatchExtrinsic 事件和运行时 DispatchError 显现,您可以使用链的元数据对其进行解码。本文解释了该机制,提供了可运行的 @polkadot/api 脚本以捕获和解码这些错误,并给出了标签到修复的清单。

直接回答:1010 无效交易真正意味着什么

当您通过 author_submitExtrinsicauthor_submitAndWatchExtrinsic 向 Polkadot 或 Substrate 节点提交外部交易时,节点的交易池会在接受交易之前执行有效性检查。如果该检查失败,RPC 返回一个 JSON-RPC 错误,代码为 1010,消息为 Invalid Transaction。实际原因编码在 data 字段中,作为诸如 PaymentStaleTemporarilyBanned 的标签。此标签不是随机字符串;它对应于 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 更新,包括池拒绝的 InvalidDrop,以及成功包含的 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: 64api.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 – 令牌错误,如 NoFundsBelowMinimum

  • Arithmetic – 算术溢出或下溢。

  • Other – 其他错误的包罗万象。

要解码 Module 错误,您需要运行时元数据。@polkadot/api 提供了一个辅助函数:api.registry.findMetaError({ index, error })。它返回一个包含 sectionnamedocs 的对象。例如,如果您得到 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 错误和调度失败,您可以更有效地调试您的外部交易。要进一步深入:

  • 如果您在平行链上构建,请查阅链自己的文档以了解自定义交易扩展和错误。

如果您需要可靠的 RPC 端点,OnFinality 提供公共和私有端点;有关详细信息,请参阅我们的 网络页面。对于生产使用,请考虑我们的 API 服务 以获得专门支持。

永远不用担心基础设施

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

开始