diff --git a/src/utils/ProviderUtils.ts b/src/utils/ProviderUtils.ts index 8c05ef798..0b4ebd8c5 100644 --- a/src/utils/ProviderUtils.ts +++ b/src/utils/ProviderUtils.ts @@ -15,9 +15,13 @@ interface RateLimitTask { reject: (err: any) => void; } -// This provider is a very small addition to the JsonRpcProvider that ensures that no more than `maxConcurrency` +// StaticJsonRpcProvider is used in place of JsonRpcProvider to avoid redundant eth_chainId queries prior to each +// request. This is safe to use when the back-end provider is guaranteed not to change. +// See https://docs.ethers.io/v5/api/providers/jsonrpc-provider/#StaticJsonRpcProvider + +// This provider is a very small addition to the StaticJsonRpcProvider that ensures that no more than `maxConcurrency` // requests are ever in flight. It uses the async/queue library to manage this. -class RateLimitedProvider extends ethers.providers.JsonRpcProvider { +class RateLimitedProvider extends ethers.providers.StaticJsonRpcProvider { // The queue object that manages the tasks. private queue: QueueObject; @@ -25,7 +29,7 @@ class RateLimitedProvider extends ethers.providers.JsonRpcProvider { // of the list. constructor( maxConcurrency: number, - ...jsonRpcConstructorParams: ConstructorParameters + ...jsonRpcConstructorParams: ConstructorParameters ) { super(...jsonRpcConstructorParams); @@ -59,7 +63,7 @@ function delay(s: number): Promise { return new Promise((resolve) => setTimeout(resolve, Math.round(s * 1000))); } -function formatProviderError(provider: ethers.providers.JsonRpcProvider, rawErrorText: string) { +function formatProviderError(provider: ethers.providers.StaticJsonRpcProvider, rawErrorText: string) { return `Provider ${provider.connection.url} failed with error: ${rawErrorText}`; } @@ -68,10 +72,10 @@ function createSendErrorWithMessage(message: string, sendError: any) { return { ...sendError, ...error }; } -class RetryProvider extends ethers.providers.JsonRpcProvider { - readonly providers: ethers.providers.JsonRpcProvider[]; +class RetryProvider extends ethers.providers.StaticJsonRpcProvider { + readonly providers: ethers.providers.StaticJsonRpcProvider[]; constructor( - params: ConstructorParameters[], + params: ConstructorParameters[], chainId: number, readonly nodeQuorumThreshold: number, readonly retries: number, @@ -99,17 +103,17 @@ class RetryProvider extends ethers.providers.JsonRpcProvider { const quorumThreshold = this._getQuorum(method, params); const requiredProviders = this.providers.slice(0, quorumThreshold); const fallbackProviders = this.providers.slice(quorumThreshold); - const errors: [ethers.providers.JsonRpcProvider, string][] = []; + const errors: [ethers.providers.StaticJsonRpcProvider, string][] = []; // This function is used to try to send with a provider and if it fails pop an element off the fallback list to try // with that one. Once the fallback provider list is empty, the method throws. Because the fallback providers are // removed, we ensure that no provider is used more than once because we care about quorum, making sure all // considered responses come from unique providers. const tryWithFallback = ( - provider: ethers.providers.JsonRpcProvider - ): Promise<[ethers.providers.JsonRpcProvider, any]> => { + provider: ethers.providers.StaticJsonRpcProvider + ): Promise<[ethers.providers.StaticJsonRpcProvider, any]> => { return this._trySend(provider, method, params) - .then((result): [ethers.providers.JsonRpcProvider, any] => [provider, result]) + .then((result): [ethers.providers.StaticJsonRpcProvider, any] => [provider, result]) .catch((err) => { // Append the provider and error to the error array. errors.push([provider, err?.stack || err?.toString()]); @@ -166,7 +170,7 @@ class RetryProvider extends ethers.providers.JsonRpcProvider { const fallbackResults = await Promise.allSettled( fallbackProviders.map((provider) => this._trySend(provider, method, params) - .then((result): [ethers.providers.JsonRpcProvider, any] => [provider, result]) + .then((result): [ethers.providers.StaticJsonRpcProvider, any] => [provider, result]) .catch((err) => { errors.push([provider, err?.stack || err?.toString()]); throw new Error("No fallbacks during quorum search"); @@ -207,7 +211,7 @@ class RetryProvider extends ethers.providers.JsonRpcProvider { return quorumResult; } - _trySend(provider: ethers.providers.JsonRpcProvider, method: string, params: Array): Promise { + _trySend(provider: ethers.providers.StaticJsonRpcProvider, method: string, params: Array): Promise { let promise = provider.send(method, params); for (let i = 0; i < this.retries; i++) { promise = promise.catch(() => delay(this.delay).then(() => provider.send(method, params)));