ethers.js多RPC端点RetryProvider初始化异常问题求助
问题分析与解决方案
核心问题
你的代码存在两个关键问题,导致切换URL后仍请求初始错误地址:
继承导致的端点固化
你继承了ethers.JsonRpcProvider,父类在构造时已将端点固定为第一个URL。后续调用super.send()始终使用这个初始端点,不会随currentIndex的递增而切换,因此无论索引怎么变,实际请求的都是badurl.eth.com。重试逻辑断裂
失败后创建的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
相关产品推荐
相关产品推荐

