From ab3f840f2a376fbd62fc9ab17a347e43f6cb0d43 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Wed, 21 Sep 2022 16:33:25 +0200 Subject: [PATCH] improve(ProviderUtils): Use StaticJsonRpcProvider Since ethers v5, all RPC requests have been preceed by an eth_chainId lookup. This is described by ethers as a safety feature to mitigate being "RPC rugged" - i.e. where a wallet silently changes RPC without notifying the provider. Inspecting the Across logs, eth_chainId is the second-most popular RPC call, after only eth_getLogs. Over the past 30 days, Infura usage has been: eth_getLogs 264M eth_chainId 30M eth_call 24M eth_getTransactionReceipt 11M eth_blockNumber 3M Total 336M Given that the Across bots maintain a 1:1 relationship between provider instance and back-end RPC provider, there's no obvious way for the chainId to ever change. The StaticJsonRpcProvider is therefore provided by ethers for this scenario. It should be noted that the 30 day figures might be lower than normal due to downtime for both Arbitrum Nitro and the merge. In any case, migrating to StaticJsonRpcProvider would reduce total requests by about 9% based on these figures, and would predominantly help reduce latency in the bots. See also: https://github.com/ethers-io/ethers.js/issues/901 Ref: ACX-67 --- src/utils/ProviderUtils.ts | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) 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)));