我正在尝试创建一个 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 的错误。
providers
向外部致电
provider = providers[chainId]
分享我实现这一目标的简单方法:
给出 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);
}
}
}