如何在 ethers.js 中为多个 RPC 端点初始化 RetryProvider

问题描述 投票:0回答:1

我正在尝试创建一个 RetryProvider 函数,该函数将初始化连接,向 RPC 端点发送请求,如果响应无效(由于速率限制、错误的 url 等),则将 rpcUrls 的 URL 索引增加1 并重试下一个 URL。

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 extends ethers.JsonRpcProvider {
  constructor(rpcUrls, chainId) {
    super(rpcUrls[0], chainId);
    this.rpcUrls = rpcUrls;
    this.currentIndex = 0;
    this.chainId = chainId;
  }

  async send(method, params) {
    try {
      console.log(
        `Attempting request with URL: ${this.rpcUrls[this.currentIndex]}`
      );
      return await super.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.");
      } else {
        console.log(
          `Retrying with next URL: ${this.rpcUrls[this.currentIndex]}`
        );
        // Recreate the provider with the next URL
        const nextProvider = new ethers.JsonRpcProvider(
          this.rpcUrls[this.currentIndex],
          this.chainId
        );
        return nextProvider.send(method, params);
      }
    }
  }
}

function createRetryProvider(chainId) {
  const urls = Object.values(rpcUrls[chainId]);
  return new RetryJsonRpcProvider(urls, chainId);
}

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

我收到了这个回复。

Attempting request with URL: https://badurl.eth.com/rpc
Attempting request with URL: https://badurl.eth.com/rpc
Request failed for URL: https://badurl.eth.com/rpc. Reason: getaddrinfo ENOTFOUND badurl.eth.com
Retrying with next URL: https://rpc.ankr.com/eth/5b280c7544a...
Request failed for URL: https://rpc.ankr.com/eth/5b280c7544a... Reason: getaddrinfo ENOTFOUND badurl.eth.com
Retrying with next URL: https://go.getblock.io/019412da1d9...
Attempting request with URL: https://go.getblock.io/019412da1d9...
Attempting request with URL: https://go.getblock.io/019412da1d9...
Request failed for URL: https://go.getblock.io/019412da1d9... Reason: getaddrinfo ENOTFOUND badurl.eth.com
Retrying with next URL: https://lb.nodies.app/v1/5e9daed367d145...
Request failed for URL: https://lb.nodies.app/v1/5e9daed367d145.... Reason: getaddrinfo ENOTFOUND badurl.eth.com
Retrying with next URL: https://eth-mainnet.public.blastapi.io
Attempting request with URL: https://eth-mainnet.public.blastapi.io
Attempting request with URL: https://eth-mainnet.public.blastapi.io
Request failed for URL: https://eth-mainnet.public.blastapi.io. Reason: getaddrinfo ENOTFOUND badurl.eth.com
Retrying with next URL: https://ethereum.publicnode.com
Request failed for URL: https://ethereum.publicnode.com. Reason: getaddrinfo ENOTFOUND badurl.eth.com
Retrying with next URL: https://eth-mainnet.g.alchemy.com/v2/SoD...

一旦初始 URL 失败,它就会尝试将 URL 加 1,但仍然显示失败 URL 的错误。

  • 最终目标是当第一个 URL 失败时,它会正确检查索引中的下一个 URL
  • 每个 URL 在单独使用时都可以工作,所以我知道它不是 RPC 连接错误
  • 它似乎没有适当地增加 URL,因为每个 URL 都会失败并出现与 badurl.eth.com 地址相关的错误。
  • 我知道这更多是一个一般性的 JS 问题,但任何帮助将不胜感激
  • 哦,我正在通过
    providers
     向外部致电 
    provider = providers[chainId]
javascript rpc ethers.js
1个回答
0
投票

分享我实现这一目标的简单方法:

给出 RPC 列表

  • 在它们之间分散负载
  • 可重试,如果调用失败,则尝试不同的rpc,如果全部失败,则抛出最后一个错误(可以优化甚至切换)
  • 它适用于所有类型的 rpc 调用
import { providers } from "ethers"; // v.5.7

export class RetriableStaticJsonRpcProvider extends providers.StaticJsonRpcProvider {
  providerList: providers.StaticJsonRpcProvider[];
  currentIndex = 0;
  error: any;

  constructor(rpcs: string[], chainId: number) {
    super({ url: rpcs[0] }, chainId);

    this.providerList = rpcs.map(url => new providers.StaticJsonRpcProvider({ url }, chainId));
  }

  async send(method: string, params: Array<any>, retries?: number): Promise<any> {
    let _retries = retries || 0;

    /**
     * validate retries before continue
     * base case of recursivity (throw if already try all rpcs)
     */
    this.validateRetries(_retries);

    try {
      // select properly provider
      const provider = this.selectProvider();

      // send rpc call
      return await provider.send(method, params);
    } catch (error) {
      // store error internally
      this.error = error;

      // increase retries
      _retries = _retries + 1;

      return this.send(method, params, _retries);
    }
  }

  private selectProvider() {
    // last rpc from the list
    if (this.currentIndex === this.providerList.length) {
      // set currentIndex to the seconds element
      this.currentIndex = 1;
      return this.providerList[0];
    }

    // select current provider
    const provider = this.providerList[this.currentIndex];
    // increase counter
    this.currentIndex = this.currentIndex + 1;

    return provider;
  }

  /**
   * validate that retries is equal to the length of rpc
   * to ensure rpc are called at least one time
   *
   * if that's the case, and we fail in all the calls
   * then throw the internal saved error
   */
  private validateRetries(retries: number) {
    if (retries === this.providerList.length) {
      const error = this.error;
      this.error = undefined;
      throw new Error(error);
    }
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.