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

eth_sendRawTransaction:余额不足与 Nonce 错误

通过准确理解节点比较哪些值,诊断并修复 eth_sendRawTransaction 返回的余额和 nonce 错误类别。

TL;DR

当签名交易未通过节点的提交前检查时,eth_sendRawTransaction 会返回余额和 nonce 错误。节点会验证签名,将交易 nonce 与账户的 pending nonce 进行比较,并检查余额是否覆盖 gasLimit * effectiveFee + value 以及内在 gas 下限。错误字符串直接对应哪项检查失败:'insufficient funds for gas * price + value' 表示预付费超过余额,'nonce too low' 表示 nonce 已被使用,'nonce too high' 表示前方存在缺口,'already known' 表示完全相同的交易已在交易池中。本文提供一个可运行的 Node.js 诊断脚本,用于解析签名字段、获取余额和 pending nonce,并打印哪项检查失败以及差额多少。文章还涵盖失败模式,例如节点滞后、mempool 差异以及进行中的交易,这些情况会导致余额在 latest 下正确但在 pending 下不足。

节点提交前检查的顺序

当你调用 eth_sendRawTransaction 时,节点会在将交易接受到其 mempool 之前执行一系列检查。以太坊 JSON-RPC 规范定义了方法契约,但确切的顺序和错误字符串因客户端而异,并因提供商而不同。实际上,大多数客户端遵循以下顺序:签名恢复、针对账户 pending nonce 的 nonce 验证,以及针对预付费的余额验证。

第一项检查是签名恢复。节点从 ECDSA 签名中恢复发送者地址,并验证其符合预期格式。如果失败,错误通常是 'invalid sender' 或 'invalid signature'。此检查不涉及余额或 nonce,但必须先通过,才会评估其他检查。

第二项检查是 nonce 验证。节点将交易 nonce 与账户的 pending nonce 进行比较,后者包含已在 mempool 中的交易。如果交易 nonce 低于 pending nonce,节点返回 'nonce too low'。如果高于,节点返回 'nonce too high',因为存在缺口。如果 nonce 匹配但完全相同的交易已在池中,节点返回 'already known'。

第三项检查是余额验证。节点将预付费计算为 gasLimit 乘以有效费用,再加上转账的 value。对于 EIP-1559 交易,有效费用是 maxFeePerGas,而不是 maxPriorityFeePerGas。节点还强制执行 21000 加上 calldata 成本的内在 gas 下限,如以太坊黄皮书所定义。如果余额不足,节点返回 'insufficient funds for gas * price + value'。

方法契约由 eth_sendRawTransaction、eth_getTransactionCount、eth_getBalance 和 eth_estimateGas 的以太坊 JSON-RPC 规范 定义。其与 gas limit 的乘积决定预付费的费用字段来自 EIP-1559,而内在 gas 下限(21000 加上 calldata)定义于 以太坊黄皮书

  • 签名恢复:从 ECDSA 签名验证发送者地址。
  • Nonce 验证:与 eth_getTransactionCount 使用 'pending' 区块参数返回的账户 pending nonce 进行比较。
  • 余额验证:检查 gasLimit * effectiveFee + value 是否超过账户余额。
  • 内在 gas 下限:根据黄皮书,为 21000 加上 calldata 成本。

余额不足家族:余额低于预付费

错误 'insufficient funds for gas * price + value' 表示账户余额低于节点计算的预付费。对于传统交易,预付费为 gasLimit * gasPrice + value。对于 EIP-1559 交易,节点使用 maxFeePerGas * gasLimit + value,即使实际有效费用可能更低。这是因为节点必须保证发送者能够覆盖最大可能成本,如 EIP-1559 所规定。

一个常见错误是使用有效费用(baseFee + maxPriorityFeePerGas)检查余额,并得出账户有足够资金的结论。节点检查的是最大值,因此即使有效费用可负担,maxFeePerGas 较高的交易也可能失败。纠正措施是降低 maxFeePerGas 或增加账户余额。

另一种变体是余额覆盖了 value 但未覆盖费用。例如,如果账户恰好只有要发送的 value 而没有额外的 gas 费用,节点会返回相同的 'insufficient funds' 错误。诊断必须将 value 部分与费用部分分开,以确定哪部分不足。

内在 gas 下限也是预付费的一部分。即使 gasLimit 设置正确,节点也要求余额至少覆盖 21000 gas 加上 calldata 成本。gasLimit 低于内在下限的交易会在余额检查之前被拒绝,但如果 gasLimit 高于下限,余额必须覆盖完整的 gasLimit * effectiveFee。

  • 传统交易:预付费 = gasLimit * gasPrice + value。
  • EIP-1559:预付费 = gasLimit * maxFeePerGas + value,而非有效费用。
  • 内在 gas 下限:根据黄皮书,为 21000 加上 calldata 成本。
  • 纠正措施:降低 maxFeePerGas 或增加余额。

Nonce 家族:过低、过高和已存在

Nonce 错误各不相同,需要不同的纠正措施。'nonce too low' 表示交易 nonce 低于账户的 pending nonce。这发生在交易已被打包,或缺口已被另一笔交易填补时。纠正措施是再次获取 pending nonce 并使用正确的值重新提交。

'nonce too high' 表示交易 nonce 高于 pending nonce,表明前方存在缺口。节点不会接受该交易,直到缺口被填补。纠正措施是等待缺失的 nonce 被填补,或先提交缺失的交易。

'already known' 表示完全相同的交易已在 mempool 中。这并非传统意义上的错误;它表明节点之前已见过该交易。纠正措施是等待确认,或者如果交易卡住,则用更高的费用替换它。替换交易定价过低与卡住的交易 指南涵盖了替换机制。

pending 与 latest nonce 的区分至关重要。使用 'pending' 区块参数的 eth_getTransactionCount 返回包含 mempool 交易的 nonce,而 'latest' 返回最后一个已打包区块的 nonce。滞后的节点可能返回落后于网络的 'latest' nonce,如果你使用错误的参数,会导致虚假的 'nonce too low'。提交时始终使用 'pending'。

  • nonce too low:nonce 已被使用或缺口已填补;重新获取 pending nonce。
  • nonce too high:前方存在缺口;填补缺口或等待。
  • already known:完全相同的交易在池中;等待或替换。
  • 提交时使用 eth_getTransactionCount 的 'pending',而非 'latest'。
# Read pending and latest nonce, then the balance, before resubmitting.
curl -s -X POST "$RPC_URL" -H 'Content-Type: application/json' --data '{"jsonrpc":"2.0","id":1,"method":"eth_getTransactionCount","params":["0xYourAddress","pending"]}'

# Compare against latest: a gap between pending and latest means in-flight transactions.
curl -s -X POST "$RPC_URL" -H 'Content-Type: application/json' --data '{"jsonrpc":"2.0","id":2,"method":"eth_getTransactionCount","params":["0xYourAddress","latest"]}'

# eth_getBalance at pending is the value the node checks against gas*price+value.
curl -s -X POST "$RPC_URL" -H 'Content-Type: application/json' --data '{"jsonrpc":"2.0","id":3,"method":"eth_getBalance","params":["0xYourAddress","pending"]}'

重新提交前重新计算预付费

在重新提交失败的交易之前,从签名字段重新计算预付费。使用 eth_getBalance 获取账户余额,使用 eth_getTransactionCount 获取 pending nonce。然后解析签名交易以提取 gasLimit、maxFeePerGas(或 gasPrice)和 value。对于 EIP-1559,计算预付费为 gasLimit * maxFeePerGas + value;对于传统交易,为 gasLimit * gasPrice + value。

将计算出的预付费与余额进行比较。如果余额不足,差额就是缺口。如果问题在于 nonce,则将交易 nonce 与 pending nonce 进行比较。这种重新计算避免了在交易本身格式错误时归咎于节点的重试循环。

使用 eth_getTransactionCount 进行 EVM nonce 管理 指南提供了关于 nonce 跟踪的更多背景。关于费用估算,使用 eth_feeHistory 估算 gas 价格 指南解释了如何推导安全的 maxFeePerGas。eth_estimateGas 与推导安全的 gas limit 指南涵盖了 gas limit 估算。

  • 使用 eth_getBalance 在 'pending' 下获取余额。
  • 使用 eth_getTransactionCount 在 'pending' 下获取 nonce。
  • 解析签名字段:gasLimit、maxFeePerGas、value、nonce。
  • 计算预付费并与余额比较。

可运行的 Node.js 余额与 Nonce 错误诊断脚本

以下 Node.js 脚本解析签名交易,获取账户余额和 pending nonce,并打印哪项检查失败以及差额多少。它使用 ethers.js 进行 RLP 解码和 JSON-RPC 调用。将 RPC_URL 和 RAW_TX 常量替换为你自己的值。

该脚本为传统交易和 EIP-1559 交易计算预付费,将其与余额比较,并检查 nonce 与 pending nonce。它为每个错误字符串打印诊断消息,包括计算出的差额和纠正措施。

const { ethers } = require('ethers');

const RPC_URL = 'https://your-rpc-endpoint';
const RAW_TX = '0x...';

async function diagnose() {
  const provider = new ethers.JsonRpcProvider(RPC_URL);
  const tx = ethers.Transaction.from(RAW_TX);
  const from = tx.from;

  const [balance, pendingNonce, latestNonce] = await Promise.all([
    provider.getBalance(from, 'pending'),
    provider.getTransactionCount(from, 'pending'),
    provider.getTransactionCount(from, 'latest')
  ]);

  const gasLimit = tx.gasLimit;
  const value = tx.value;
  const maxFee = tx.maxFeePerGas ?? tx.gasPrice;
  const upfront = gasLimit * maxFee + value;

  console.log('From:', from);
  console.log('Balance:', ethers.formatEther(balance));
  console.log('Pending nonce:', pendingNonce);
  console.log('Latest nonce:', latestNonce);
  console.log('Tx nonce:', tx.nonce);
  console.log('Upfront cost:', ethers.formatEther(upfront));

  if (balance < upfront) {
    const shortfall = upfront - balance;
    console.log('ERROR: insufficient funds for gas * price + value');
    console.log('Shortfall:', ethers.formatEther(shortfall));
  }

  if (tx.nonce < pendingNonce) {
    console.log('ERROR: nonce too low');
    console.log('Expected nonce:', pendingNonce);
  } else if (tx.nonce > pendingNonce) {
    console.log('ERROR: nonce too high');
    console.log('Gap ahead:', tx.nonce - pendingNonce);
  }
}

diagnose().catch(console.error);

针对自己端点进行测量的结果表

使用下表记录针对你自己端点的诊断结果。填写错误字符串、计算出的差额、pending nonce、latest nonce 以及纠正措施。此表可帮助你跟踪多次提交中的模式,并识别问题是与余额相关还是与 nonce 相关。

对每笔失败的交易运行诊断脚本并记录值。如果差额为正,则余额不足。如果 pending nonce 与 latest nonce 不同,则存在进行中的交易。如果交易 nonce 低于 pending nonce,则 nonce 过低。如果高于,则 nonce 过高。

  • 错误字符串:eth_sendRawTransaction 返回的确切消息。
  • 计算出的差额:预付费减去余额,如果为正。
  • Pending nonce:来自 eth_getTransactionCount 的 'pending'。
  • Latest nonce:来自 eth_getTransactionCount 的 'latest'。
  • 纠正措施:降低 maxFeePerGas、增加余额或调整 nonce。

失败模式:节点滞后、Mempool 差异和进行中的交易

滞后的节点可能返回落后于网络的 'latest' nonce,如果你使用错误的区块参数,会导致虚假的 'nonce too low'。提交时始终使用 'pending'。如果节点严重滞后,考虑切换到其他端点。以太坊 RPC URL 与端点选择(RPC Assistant) 指南涵盖了端点选择。

Mempool 差异可能导致在一个端点上出现 'already known',而在另一个端点上被接受。这是因为不同节点具有不同的 mempool 策略和传播延迟。如果你在一个端点上看到 'already known',请尝试另一个端点或等待传播。调试 JSON-RPC -32603 内部错误 指南涵盖了相关的错误处理。

余额在 'latest' 下正确但在 'pending' 下不足,表明存在尚未打包的进行中交易。pending 余额包含 mempool 交易的扣除,因此可用余额更低。重新提交前始终检查 'pending' 下的余额。如果 pending 下余额不足,请等待进行中的交易确认或替换它。

  • 节点滞后:使用 'pending' nonce,而非 'latest'。
  • Mempool 差异:'already known' 可能因端点而异。
  • 进行中的交易:'pending' 下的余额低于 'latest'。
  • 纠正措施:等待、替换或切换端点。

限制与权衡:客户端特定错误和重试影响

确切的错误字符串因客户端而异,并因提供商而不同。Geth、Erigon、Nethermind 和 Besu 可能对相同的底层条件返回略有不同的消息。以太坊 JSON-RPC 规范定义了方法契约,但未定义错误字符串。始终将错误字符串视为提示,并使用诊断脚本验证底层条件。

Mempool 节点差异可能改变相同字节的观察到的错误。在一个节点上 'already known' 的交易可能在另一个节点上被接受。这具有重试和幂等性影响:在不同端点上重试同一笔交易可能成功,但如果第一个端点最终传播了它,也可能创建重复。为每笔交易使用唯一的 nonce,并监控确认。

重试循环应当是幂等的。在重新提交之前,重新计算预付费和 nonce。如果交易已在池中,请等待确认而不是重新提交。如果余额不足,增加余额或降低费用。如果 nonce 错误,再次获取 pending nonce 并使用正确的值重新提交。

  • 错误字符串因客户端而异,并因提供商而不同。
  • Mempool 差异可能改变相同字节的观察到的错误。
  • 重试循环应当幂等:重新提交前重新计算。
  • 为每笔交易使用唯一的 nonce,并监控确认。

余额与 Nonce 错误排查清单

当 eth_sendRawTransaction 失败时,请按照此清单识别根本原因。首先,解析签名交易以提取 nonce、gasLimit、maxFeePerGas(或 gasPrice)和 value。其次,获取账户余额和 pending nonce。第三,计算预付费并与余额比较。第四,将交易 nonce 与 pending nonce 比较。

如果错误是 'insufficient funds for gas * price + value',检查余额是否覆盖预付费。如果余额覆盖了 value 但未覆盖费用,差额就是费用部分。如果 maxFeePerGas 较高,降低它或增加余额。如果错误是 'nonce too low',重新获取 pending nonce 并重新提交。如果错误是 'nonce too high',填补缺口或等待。如果错误是 'already known',等待确认或替换交易。

关于费用估算的更多背景,请参阅 使用 eth_feeHistory 估算 gas 价格 指南。关于 gas limit 估算,请参阅 eth_estimateGas 与推导安全的 gas limit 指南。关于 nonce 管理,请参阅 使用 eth_getTransactionCount 进行 EVM nonce 管理 指南。

  • 解析签名字段:nonce、gasLimit、maxFeePerGas、value。
  • 获取余额和 pending nonce。
  • 计算预付费并与余额比较。
  • 将交易 nonce 与 pending nonce 比较。
  • 根据错误字符串应用纠正措施。

后续步骤:监控、自动化和端点选择

解决当前错误后,考虑自动化诊断。将重新计算逻辑集成到你的交易提交管道中,以便在余额和 nonce 问题到达节点之前捕获它们。定期监控 pending nonce 和余额,以检测进行中的交易和资金缺口。

对于生产工作负载,使用可靠的 RPC 端点。以太坊 RPC URL 与端点选择(RPC Assistant) 指南涵盖了端点选择标准。RPC 定价 页面提供了定价计划信息。API 服务 页面描述了 API 服务产品。

更多故障排查指南,请访问 OnFinality Learn 中心以太坊网络页面 提供了网络特定细节。替换交易定价过低与卡住的交易 指南涵盖了替换错误类别,这与本文涵盖的余额和 nonce 错误不同。

  • 在提交管道中自动化重新计算。
  • 定期监控 pending nonce 和余额。
  • 生产环境使用可靠的 RPC 端点。
  • 查看相关指南以了解替换和费用估算。

永远不用担心基础设施

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

开始