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

Solana RPC 超时:原因、重试策略与可靠交易发送

了解 Solana RPC 调用超时的原因,如何区分传输层超时与方法层超时,并使用 @solana/web3.js 实现稳健的交易发送重试策略。

TL;DR

Solana RPC 超时可能发生在传输层或方法层。本文解释了原因,提供了使用 Node.js 发送交易的重试模式,并给出了故障排查清单。

直接回答:Solana RPC 超时的原因及处理方法

Solana RPC 超时是开发人员常见的痛点。它们可能源于网络问题、公共端点过载或诸如 getProgramAccounts 之类的重查询。关键是要区分传输层超时(连接/读取)和方法层超时(RPC 方法本身耗时过长)。对于发送交易,你需要一个超越简单 HTTP 重试的重试策略:你必须处理 ExpiredBlockhashError 并使用 getSignatureStatuses 来确认最终性。本文提供了诊断和修复超时的实用指南,并附带一个可运行的 Node.js 示例。

简而言之,始终在 HTTP 客户端上设置显式超时,使用可靠的 RPC 提供商,并实现一个尊重 Solana 区块哈希过期(约 2 个时隙)和交易费用影响的重试循环。

  • 传输层超时:HTTP 客户端上的连接和读取超时。
  • 方法层超时:RPC 方法响应时间过长(例如 getProgramAccounts)。
  • 交易确认:使用 getSignatureStatuses 配合重试循环,而不仅仅是 sendTransaction

理解 Solana RPC 超时行为

Solana RPC 端点是 HTTP/2 JSON-RPC 服务器。超时可能发生在两层:传输层(TCP 连接、TLS 握手或读取响应)和方法层(服务器处理请求)。传输层超时通常在你的 HTTP 客户端(例如 fetchaxios)中配置。方法层超时不能直接配置;它们取决于服务器的处理时间和方法的复杂度。

api.mainnet-beta.solana.com 这样的公共端点通常有速率限制和负载均衡,但在高负载下仍可能变慢。集群端点(如 OnFinality 提供的)将请求分发到多个节点,但即便如此,某些方法也可能很慢。例如,getSignaturesForAddressgetBlock 可能因为扫描大量数据而变慢。getProgramAccounts 以重量级著称,如果过滤不当可能会导致超时。

当你发送交易时,sendTransaction 会快速返回交易签名,但这并不意味着交易已确认。确认需要轮询 getSignatureStatuses,直到交易达到所需的承诺级别(例如 confirmedfinalized)。这是一个单独的步骤,需要自己的超时和重试逻辑。

  • 传输层超时:在 HTTP 客户端中设置 connectTimeoutreadTimeout
  • 方法层超时:使用带过滤器的 getProgramAccounts 以减少数据负载。
  • 交易确认:使用超时和重试轮询 getSignatureStatuses

传输层超时与方法层超时:你需要了解的内容

传输层超时最容易处理。在 Node.js 中,你可以使用 AbortController 或像 axios 这样的库设置 timeout 选项。例如,RPC 调用通常设置 10 秒超时。如果服务器在该时间内未响应,请求将被中止。

方法层超时则更棘手。服务器可能接受请求,但处理时间很长。例如,不带过滤器的 getProgramAccounts 可能会扫描整个账户空间,导致超时。Solana 文档建议使用过滤器和 dataSlice 来限制响应大小。同样,如果地址有很多交易,getSignaturesForAddress 可能会很慢;使用 limitbefore 参数进行分页。

使用公共端点时,你可能还会遇到速率限制,这表现为超时或 HTTP 429 错误。OnFinality 的 RPC Assistant 可以帮助你选择性能和可靠性更好的提供商。

  • 设置传输层超时以避免请求挂起。
  • 使用过滤器和分页优化方法调用。
  • 考虑使用专用 RPC 提供商以减少速率限制并提高一致性。

为什么发送交易需要不同的重试策略

当你发送交易时,RPC 方法 sendTransaction 仅将交易广播到集群。它不保证包含在区块中。交易有一个区块哈希,大约在 2 个时隙(约 1.6 秒)后过期。如果区块哈希在交易处理之前过期,你会收到 ExpiredBlockhashError。因此,简单的 HTTP 重试(重新发送相同的交易)会失败,因为区块哈希不再有效。

要可靠地发送交易,你必须实现一个重试循环:1) 获取新的区块哈希,2) 签署新交易,3) 发送它,4) 轮询 getSignatureStatuses 直到交易被确认或超时。@solana/web3.js 库提供了带有 maxRetries 选项的 sendTransaction,但它不会自动处理区块哈希刷新。你需要手动处理 ExpiredBlockhashErrorBlockhashNotFound

Solana 官方文档关于 重试交易 说明,你应该使用 getSignatureStatuses 检查状态,并在必要时使用新的区块哈希重新提交。这对生产应用程序至关重要。

  • 区块哈希很快过期(约 2 个时隙)。
  • sendTransaction 仅广播;确认需要轮询。
  • 通过刷新区块哈希并重新签名来处理 ExpiredBlockhashError

可运行示例:使用退避重试发送交易的 Node.js 代码

下面是一个完整的 Node.js 脚本,演示了发送交易的稳健重试策略。它使用 @solana/web3.js 并包含指数退避。该脚本创建了一个简单的转账交易,带重试发送,并轮询确认。

要运行它,请安装依赖:npm install @solana/web3.js。将 PRIVATE_KEYRPC_URL 替换为你自己的。脚本将输出交易签名和确认状态。

const { Connection, Keypair, SystemProgram, Transaction, LAMPORTS_PER_SOL, sendAndConfirmTransaction } = require('@solana/web3.js');

// Replace with your private key (array of 64 numbers) and RPC URL
const PRIVATE_KEY = [/* ... */];
const RPC_URL = 'https://api.mainnet-beta.solana.com'; // or your OnFinality endpoint

const connection = new Connection(RPC_URL, 'confirmed');
const from = Keypair.fromSecretKey(Uint8Array.from(PRIVATE_KEY));
const to = Keypair.generate().publicKey;

async function sendWithRetry(connection, from, to, amount, maxRetries = 5) {
  let retries = 0;
  while (retries < maxRetries) {
    try {
      // Get a fresh blockhash
      const { blockhash } = await connection.getLatestBlockhash('confirmed');
      const transaction = new Transaction().add(
        SystemProgram.transfer({
          fromPubkey: from.publicKey,
          toPubkey: to,
          lamports: amount,
        })
      );
      transaction.recentBlockhash = blockhash;
      transaction.feePayer = from.publicKey;

      // Sign and send
      transaction.sign(from);
      const signature = await connection.sendRawTransaction(transaction.serialize());
      console.log(`Transaction sent: ${signature}`);

      // Confirm with timeout
      const confirmation = await connection.confirmTransaction(signature, 'confirmed');
      if (confirmation.value.err) {
        throw new Error(`Transaction failed: ${confirmation.value.err}`);
      }
      console.log(`Transaction confirmed: ${signature}`);
      return signature;
    } catch (error) {
      if (error.message.includes('ExpiredBlockhashError') || error.message.includes('BlockhashNotFound')) {
        console.log('Blockhash expired, retrying with new blockhash...');
      } else {
        console.error('Error:', error.message);
      }
      retries++;
      // Exponential backoff: 1s, 2s, 4s, 8s, 16s
      const delay = Math.pow(2, retries) * 1000;
      console.log(`Retrying in ${delay / 1000}s...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  throw new Error('Max retries exceeded');
}

(async () => {
  try {
    const signature = await sendWithRetry(connection, from, to, 0.001 * LAMPORTS_PER_SOL);
    console.log('Final signature:', signature);
  } catch (error) {
    console.error('Failed to send transaction:', error.message);
  }
})();

预期输出及如何验证

当你运行脚本时,你应该看到类似以下的输出:

Transaction sent: 5Ux...
Transaction confirmed: 5Ux...
Final signature: 5Ux...

如果区块哈希过期,你会看到 'Blockhash expired, retrying with new blockhash...' 以及随后的延迟。脚本最终会确认交易,或在达到最大重试次数后抛出错误。

要验证交易,你可以使用 Solana Explorer 或 getSignatureStatuses 检查状态。脚本已经使用 confirmed 承诺级别确认,这足以满足大多数用例。对于最终性,你可以将承诺级别改为 'finalized'。

  • 脚本打印交易签名和确认状态。
  • 你可以使用签名在 Solana Explorer 上验证。
  • 根据你的需求调整 maxRetries 和退避参数。

常见超时来源和故障排查清单

以下是 Solana RPC 超时的常见来源及修复方法:

  1. 重量级 getProgramAccounts 调用:使用过滤器和 dataSlice 减少数据。例如,使用 dataSize 过滤器的 getProgramAccounts 会快得多。

  1. getBalance 轮询:如果你频繁轮询 getBalance,考虑使用 WebSocket 订阅而不是轮询。

  1. 负载削减和集群降级:在网络拥塞期间,RPC 节点可能会削减负载,导致超时。使用像 OnFinality 的 Solana 网络页面 这样的可靠提供商来缓解这个问题。

  1. 不正确的承诺级别:使用 finalized 可能更慢;使用 confirmed 以获得更快的响应。

  1. 网络问题:检查你的互联网连接和防火墙设置。

  1. 速率限制:公共端点经常有速率限制;使用专用 RPC 提供商以避免这种情况。

  • 使用过滤器优化 getProgramAccounts
  • 使用 WebSocket 订阅获取实时数据。
  • 选择可靠的 RPC 提供商。
  • 设置适当的承诺级别。
  • 监控网络健康状况。

重试策略的权衡与限制

虽然使用新的区块哈希重试至关重要,但它也有权衡。每次重试都会消耗一个新的区块哈希,并且如果交易被部分处理,可能会产生交易费用。此外,过于激进的重试会增加网络负载。平衡重试次数和退避延迟很重要。

另一个限制是,即使交易被丢弃,sendTransaction 也可能返回签名。轮询 getSignatureStatuses 是确认所必需的,但这会增加延迟。对于高吞吐量的应用程序,考虑使用 @solana/web3.js 中的 sendAndConfirmTransaction,它在内部处理确认,但它仍然存在相同的区块哈希问题。

最后,如果网络严重降级,任何重试策略都无法保证成功。在这种情况下,最好快速失败并提醒用户,而不是无限重试。

  • 重试会消耗区块哈希并可能产生费用。
  • 确认轮询会增加延迟。
  • 在网络严重降级时快速失败。

后续步骤和更多资源

为了提高 Solana RPC 的可靠性,考虑使用像 OnFinality 这样的专用 RPC 提供商。我们的 Solana 网络页面 提供高性能端点,具有低延迟和高可用性。你还可以探索我们的 定价 了解免费和付费层级。

如需更多故障排查技巧,请查看我们的 通用 RPC 超时诊断和修复 指南。如果你正在 Solana 上构建,我们的 API 服务 提供额外的工具和分析。

我们还建议阅读 Solana 官方文档中的 RPC API重试交易 以加深理解。有关性能最佳实践,请参阅 Helius 的 Solana RPC 优化 指南。

  • 使用 OnFinality 获取可靠的 Solana RPC 端点。
  • 阅读 Solana 官方文档了解 RPC 和重试。
  • 探索 Helius 的优化指南。

永远不用担心基础设施

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

开始