You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ethers.js多RPC端点RetryProvider初始化异常问题求助

问题分析与解决方案

核心问题

你的代码存在两个关键问题,导致切换URL后仍请求初始错误地址:

  1. 继承导致的端点固化
    你继承了ethers.JsonRpcProvider,父类在构造时已将端点固定为第一个URL。后续调用super.send()始终使用这个初始端点,不会随currentIndex的递增而切换,因此无论索引怎么变,实际请求的都是badurl.eth.com。

  2. 重试逻辑断裂
    失败后创建的nextProvider是普通的JsonRpcProvider实例,它的send方法没有你的重试逻辑。一旦这个新provider请求失败,会直接抛出错误,不会继续尝试后续URL;同时日志打印的是当前索引对应的URL,但实际错误来自初始端点,造成日志与错误信息矛盾。

修复后的代码

采用组合而非继承的方式,内部维护当前provider实例,动态切换端点并保留完整重试逻辑:

import { ethers } from "ethers";

const rpcUrls = {
  1: {
    0: "https://badurl.eth.com/rpc",
    1: "https://rpc.ankr.com/eth/5b280...",
    2: "https://go.getblock.io/01941...",
    3: "https://lb.nodies.app/v1/5e9daed36...",
    4: "https://eth-mainnet.public.blastapi.io",
    5: "https://ethereum.publicnode.com",
    6: "https://eth-mainnet.g.alchemy.com/v2/SoDlWkD...",
  },
};

class RetryJsonRpcProvider {
  constructor(rpcUrls, chainId) {
    this.rpcUrls = rpcUrls;
    this.currentIndex = 0;
    this.chainId = chainId;
    // 初始化第一个provider
    this.provider = new ethers.JsonRpcProvider(rpcUrls[0], chainId);
  }

  async send(method, params) {
    try {
      console.log(`Attempting request with URL: ${this.rpcUrls[this.currentIndex]}`);
      return await this.provider.send(method, params);
    } catch (error) {
      console.log(`Request failed for URL: ${this.rpcUrls[this.currentIndex]}. Reason: ${error.message}`);
      this.currentIndex++;

      if (this.currentIndex >= this.rpcUrls.length) {
        console.log("All RPC URLs failed.");
        throw new Error("All RPC URLs failed.");
      }

      console.log(`Retrying with next URL: ${this.rpcUrls[this.currentIndex]}`);
      // 更新为下一个URL的provider
      this.provider = new ethers.JsonRpcProvider(this.rpcUrls[this.currentIndex], this.chainId);
      // 递归调用自身send,继续重试逻辑
      return this.send(method, params);
    }
  }

  // 代理常用方法到当前provider(根据需求添加更多)
  async getBalance(address, blockTag) {
    return this.provider.getBalance(address, blockTag);
  }

  async getBlock(blockHashOrBlockNumber) {
    return this.provider.getBlock(blockHashOrBlockNumber);
  }

  async getCode(address, blockTag) {
    return this.provider.getCode(address, blockTag);
  }

  // 若需要支持所有JsonRpcProvider方法,可使用Proxy简化代理
  // static create(rpcUrls, chainId) {
  //   const instance = new RetryJsonRpcProvider(rpcUrls, chainId);
  //   return new Proxy(instance, {
  //     get(target, prop) {
  //       if (typeof target.provider[prop] === 'function') {
  //         return target.provider[prop].bind(target.provider);
  //       }
  //       return target.provider[prop];
  //     }
  //   });
  // }
}

function createRetryProvider(chainId) {
  const urls = Object.values(rpcUrls[chainId] || []);
  if (urls.length === 0) {
    throw new Error(`No RPC URLs configured for chainId ${chainId}`);
  }
  return new RetryJsonRpcProvider(urls, chainId);
  // 若使用Proxy,替换为:return RetryJsonRpcProvider.create(urls, chainId);
}

export const providers = {
  1: createRetryProvider(1),
  56: createRetryProvider(56),
  137: createRetryProvider(137),
  42161: createRetryProvider(42161),
  10: createRetryProvider(10),
  8453: createRetryProvider(8453),
};

关键改进点

  • 动态切换provider:每次失败后更新this.provider为下一个URL的实例,确保后续请求使用新端点。
  • 完整重试链:通过递归调用自身send方法,保证所有URL按顺序尝试,直到成功或全部失败。
  • 日志与实际请求一致:打印的URL和实际请求的端点完全匹配,避免混淆。
  • 方法代理:添加常用方法的代理(或使用Proxy简化),确保外部调用getBalance等方法时也能使用当前的provider。

内容的提问来源于stack exchange,提问作者Rory Graman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 03:54:57