From 3160b9ad387e175947f9f17398109e8af613d2bd Mon Sep 17 00:00:00 2001 From: jacobmakarsky Date: Fri, 24 Jan 2025 19:08:53 -0500 Subject: [PATCH 1/6] allow pollingInterval in fallbackProvider, do not allow WebSocketProvider in FallbackProvider as it defaults to polling which defeats purpose of WebSocketProvider --- src/utils/available-rpc.ts | 8 ++++---- src/utils/fallback-provider.ts | 25 ++++++++++++++++--------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/utils/available-rpc.ts b/src/utils/available-rpc.ts index ef2554d..8715a9f 100644 --- a/src/utils/available-rpc.ts +++ b/src/utils/available-rpc.ts @@ -68,13 +68,13 @@ const getBlockNumber = async ( // Conditionally handle what type of provider is being passed let rpcProvider: Provider; - - // If URL starts with wss, use WebSocketProvider if(provider.startsWith('wss')) { - rpcProvider = new WebSocketProvider(provider, network); + rpcProvider = new WebSocketProvider(provider, network, { + staticNetwork: network, // Network is dictated in the RPC URL, will not change + }); } else { rpcProvider = new JsonRpcProvider(provider, network, { - staticNetwork: network, + staticNetwork: network, // Network is dictated in the RPC URL, will not change }); } diff --git a/src/utils/fallback-provider.ts b/src/utils/fallback-provider.ts index f13351a..226fcd3 100644 --- a/src/utils/fallback-provider.ts +++ b/src/utils/fallback-provider.ts @@ -1,4 +1,4 @@ -import { FallbackProvider, Network, WebSocketProvider } from 'ethers'; +import { FallbackProvider, Network } from 'ethers'; import { ConfiguredJsonRpcProvider } from './configured-json-rpc-provider'; import { FallbackProviderConfig } from 'ethers/lib.commonjs/providers/provider-fallback'; @@ -17,6 +17,7 @@ export type ProviderJson = { export const createFallbackProviderFromJsonConfig = ( config: FallbackProviderJsonConfig, + pollingInterval?: number, ): FallbackProvider => { try { const totalWeight = config.providers.reduce( @@ -40,13 +41,17 @@ export const createFallbackProviderFromJsonConfig = ( maxLogsPerBatch, }) => { const isWebsocket = providerURL.startsWith('wss'); - const provider = isWebsocket - ? new WebSocketProvider(providerURL, network) - : new ConfiguredJsonRpcProvider( - providerURL, - network, - maxLogsPerBatch, - ); + if (isWebsocket) { + throw new Error( + 'WebSocketProvider not supported in FallbackProvider as it will use polling instead of eth_subscribe', + ); + } + + const provider = new ConfiguredJsonRpcProvider( + providerURL, + network, + maxLogsPerBatch, + ); const fallbackProviderConfig: FallbackProviderConfig = { provider, @@ -58,7 +63,9 @@ export const createFallbackProviderFromJsonConfig = ( }, ); - return new FallbackProvider(providers, network); + return new FallbackProvider(providers, network, { + pollingInterval, + }); } catch (cause) { if (!(cause instanceof Error)) { throw new Error( From 6cbd8bf0878e17596c109f31f487eefc6b9c62a7 Mon Sep 17 00:00:00 2001 From: jacobmakarsky Date: Fri, 24 Jan 2025 19:19:00 -0500 Subject: [PATCH 2/6] fix test to expect failure for wss provider pass to fallbackprovider --- src/utils/__tests__/available-rpc.test.ts | 18 ++++++++++++++---- src/utils/fallback-provider.ts | 8 +++----- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/utils/__tests__/available-rpc.test.ts b/src/utils/__tests__/available-rpc.test.ts index dd4e3ee..20cf193 100644 --- a/src/utils/__tests__/available-rpc.test.ts +++ b/src/utils/__tests__/available-rpc.test.ts @@ -29,7 +29,7 @@ describe('available-rpc', () => { await fallbackProvider.getBlockNumber(); }).timeout(5000); - it('Should check fallback provider cascade for FallbackProvider of WSS RPC', async () => { + it('Should fail to allow WSS RPC in a FallbackProvider', async () => { const config: FallbackProviderJsonConfig = { chainId: 1, providers: [ @@ -42,9 +42,19 @@ describe('available-rpc', () => { ], }; - const fallbackProvider = createFallbackProviderFromJsonConfig(config); - - await fallbackProvider.getBlockNumber(); + try { + // This should throw an error because WSS is not supported in FallbackProvider + createFallbackProviderFromJsonConfig(config); + throw new Error("Test should have thrown an error but did not"); + } catch (error) { + if (error instanceof Error) { + expect(error.message).to.equal( + "WebSocketProvider not supported in FallbackProvider as it will use polling instead of eth_subscribe" + ); + } else { + throw new Error("Caught an unexpected error type"); + } + } }).timeout(5000); it('Should sort ascending and descending', () => { diff --git a/src/utils/fallback-provider.ts b/src/utils/fallback-provider.ts index 226fcd3..dbcb571 100644 --- a/src/utils/fallback-provider.ts +++ b/src/utils/fallback-provider.ts @@ -64,7 +64,7 @@ export const createFallbackProviderFromJsonConfig = ( ); return new FallbackProvider(providers, network, { - pollingInterval, + pollingInterval }); } catch (cause) { if (!(cause instanceof Error)) { @@ -73,9 +73,7 @@ export const createFallbackProviderFromJsonConfig = ( { cause }, ); } - throw new Error( - `Invalid fallback provider config for chain ${config.chainId}`, - { cause }, - ); + // Preserve the original error message + throw cause; } }; From d66899425567e8b2e4259068df25c9e2aea6e27d Mon Sep 17 00:00:00 2001 From: jacobmakarsky Date: Fri, 24 Jan 2025 21:17:39 -0500 Subject: [PATCH 3/6] allow single provider creation instead of forcing fallbackprovider, by using existing ProviderJson type --- src/utils/__tests__/available-rpc.test.ts | 29 ++++++++++++--- src/utils/available-rpc.ts | 2 +- src/utils/index.ts | 2 +- .../{fallback-provider.ts => provider.ts} | 36 ++++++++++++++++--- 4 files changed, 59 insertions(+), 10 deletions(-) rename src/utils/{fallback-provider.ts => provider.ts} (64%) diff --git a/src/utils/__tests__/available-rpc.test.ts b/src/utils/__tests__/available-rpc.test.ts index 20cf193..d9ae043 100644 --- a/src/utils/__tests__/available-rpc.test.ts +++ b/src/utils/__tests__/available-rpc.test.ts @@ -2,8 +2,9 @@ import chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; import { FallbackProviderJsonConfig, - createFallbackProviderFromJsonConfig, -} from '../fallback-provider'; + ProviderJson, + createProviderFromJsonConfig, +} from '../provider'; chai.use(chaiAsPromised); const { expect } = chai; @@ -24,7 +25,7 @@ describe('available-rpc', () => { ], }; - const fallbackProvider = createFallbackProviderFromJsonConfig(config); + const fallbackProvider = createProviderFromJsonConfig(config); await fallbackProvider.getBlockNumber(); }).timeout(5000); @@ -44,7 +45,7 @@ describe('available-rpc', () => { try { // This should throw an error because WSS is not supported in FallbackProvider - createFallbackProviderFromJsonConfig(config); + createProviderFromJsonConfig(config); throw new Error("Test should have thrown an error but did not"); } catch (error) { if (error instanceof Error) { @@ -57,6 +58,26 @@ describe('available-rpc', () => { } }).timeout(5000); + it('Should fail to create single provider with missing chainId', async () => { + const config = { + provider: 'https://eth-mainnet.publicnode.com', + priority: 1, + weight: 2, + stallTimeout: 2500, + } as ProviderJson; + + try { + createProviderFromJsonConfig(config); + throw new Error("Test should have thrown an error but did not"); + } catch (error) { + if (error instanceof Error) { + expect(error.message).to.equal('chainId is required for single provider configuration'); + } else { + throw new Error("Caught an unexpected error type"); + } + } + }); + it('Should sort ascending and descending', () => { const allConfigs = [ { diff --git a/src/utils/available-rpc.ts b/src/utils/available-rpc.ts index 8715a9f..7d70b3b 100644 --- a/src/utils/available-rpc.ts +++ b/src/utils/available-rpc.ts @@ -1,6 +1,6 @@ /// import { JsonRpcProvider, Network, type Provider, WebSocketProvider } from 'ethers'; -import { ProviderJson } from './fallback-provider'; +import { ProviderJson } from './provider'; import { getUpperBoundMedian } from './median'; import { promiseTimeout } from './promises'; diff --git a/src/utils/index.ts b/src/utils/index.ts index 8a9c0c5..8480616 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,7 +1,7 @@ export * from './artifact-v2'; export * from './available-rpc'; export * from './compare'; -export * from './fallback-provider'; +export * from './provider'; export * from './error'; export * from './format'; export * from './gas'; diff --git a/src/utils/fallback-provider.ts b/src/utils/provider.ts similarity index 64% rename from src/utils/fallback-provider.ts rename to src/utils/provider.ts index dbcb571..7a6b62e 100644 --- a/src/utils/fallback-provider.ts +++ b/src/utils/provider.ts @@ -1,6 +1,7 @@ -import { FallbackProvider, Network } from 'ethers'; +import { FallbackProvider, JsonRpcProvider, Network, Provider, WebSocketProvider } from 'ethers'; import { ConfiguredJsonRpcProvider } from './configured-json-rpc-provider'; import { FallbackProviderConfig } from 'ethers/lib.commonjs/providers/provider-fallback'; +import { isDefined } from './util'; export type FallbackProviderJsonConfig = { chainId: number; @@ -11,15 +12,42 @@ export type ProviderJson = { priority: number; weight: number; provider: string; + chainId?: number; stallTimeout?: number; maxLogsPerBatch?: number; }; -export const createFallbackProviderFromJsonConfig = ( - config: FallbackProviderJsonConfig, +export const createProviderFromJsonConfig = ( + config: FallbackProviderJsonConfig | ProviderJson, pollingInterval?: number, -): FallbackProvider => { +): Provider => { try { + // Handle single provider case + if (!('providers' in config)) { + // Ensure chainId is present + if (!isDefined(config.chainId)) { + throw new Error('chainId is required for single provider configuration'); + } + + // Get RPC URL + const providerURL = config.provider; + + // Create singular provider depending on the URL + let provider: JsonRpcProvider | WebSocketProvider; + if (providerURL.startsWith('wss')) { + provider = new WebSocketProvider(providerURL, config.chainId, { + staticNetwork: true, + }); + } else { + provider = new JsonRpcProvider(providerURL, config.chainId, { + staticNetwork: true, + pollingInterval, + }); + } + + return provider; + }; + const totalWeight = config.providers.reduce( (acc, { weight }) => acc + weight, 0, From 540ba16a5222bcaf3396575e4d20aeae789826c3 Mon Sep 17 00:00:00 2001 From: jacobmakarsky Date: Fri, 24 Jan 2025 22:29:22 -0500 Subject: [PATCH 4/6] ensure provider string exists when creating single provider --- src/utils/__tests__/available-rpc.test.ts | 24 +++++++++++++++++++++-- src/utils/provider.ts | 12 +++++++++--- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/utils/__tests__/available-rpc.test.ts b/src/utils/__tests__/available-rpc.test.ts index d9ae043..da89383 100644 --- a/src/utils/__tests__/available-rpc.test.ts +++ b/src/utils/__tests__/available-rpc.test.ts @@ -68,12 +68,32 @@ describe('available-rpc', () => { try { createProviderFromJsonConfig(config); - throw new Error("Test should have thrown an error but did not"); + expect.fail("Test should have thrown an error but did not"); } catch (error) { if (error instanceof Error) { expect(error.message).to.equal('chainId is required for single provider configuration'); } else { - throw new Error("Caught an unexpected error type"); + expect.fail("Caught an unexpected error type"); + } + } + }); + + it('Should fail to create single provider with missing provider URL', async () => { + const config = { + chainid: 1, + priority: 1, + weight: 2, + stallTimeout: 2500, + }; + + try { + createProviderFromJsonConfig(config as unknown as ProviderJson); + expect.fail("Test should have thrown an error but did not"); + } catch (error) { + if (error instanceof Error) { + expect(error.message).to.equal('provider is required for single provider configuration'); + } else { + expect.fail("Caught an unexpected error type"); } } }); diff --git a/src/utils/provider.ts b/src/utils/provider.ts index 7a6b62e..02e3554 100644 --- a/src/utils/provider.ts +++ b/src/utils/provider.ts @@ -24,13 +24,19 @@ export const createProviderFromJsonConfig = ( try { // Handle single provider case if (!('providers' in config)) { + // Get RPC URL + const providerURL = config.provider; + // Ensure providerURL exists and is a string + if (!isDefined(providerURL)) { + throw new Error('provider is required for single provider configuration'); + } else if (typeof providerURL !== 'string') { + throw new Error('provider must be a string'); + } + // Ensure chainId is present if (!isDefined(config.chainId)) { throw new Error('chainId is required for single provider configuration'); } - - // Get RPC URL - const providerURL = config.provider; // Create singular provider depending on the URL let provider: JsonRpcProvider | WebSocketProvider; From 4b9542a23f9775fda953349a07427ee07950ca4f Mon Sep 17 00:00:00 2001 From: jacobmakarsky Date: Sat, 25 Jan 2025 15:35:20 -0500 Subject: [PATCH 5/6] be more specific in provider return type --- src/utils/provider.ts | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/utils/provider.ts b/src/utils/provider.ts index 02e3554..c6a95d7 100644 --- a/src/utils/provider.ts +++ b/src/utils/provider.ts @@ -1,5 +1,4 @@ -import { FallbackProvider, JsonRpcProvider, Network, Provider, WebSocketProvider } from 'ethers'; -import { ConfiguredJsonRpcProvider } from './configured-json-rpc-provider'; +import { FallbackProvider, JsonRpcProvider, WebSocketProvider } from 'ethers'; import { FallbackProviderConfig } from 'ethers/lib.commonjs/providers/provider-fallback'; import { isDefined } from './util'; @@ -20,7 +19,7 @@ export type ProviderJson = { export const createProviderFromJsonConfig = ( config: FallbackProviderJsonConfig | ProviderJson, pollingInterval?: number, -): Provider => { +): FallbackProvider | WebSocketProvider | JsonRpcProvider => { try { // Handle single provider case if (!('providers' in config)) { @@ -52,7 +51,7 @@ export const createProviderFromJsonConfig = ( } return provider; - }; + } const totalWeight = config.providers.reduce( (acc, { weight }) => acc + weight, @@ -64,8 +63,6 @@ export const createProviderFromJsonConfig = ( ); } - const network = Network.from(Number(config.chainId)); - const providers: FallbackProviderConfig[] = config.providers.map( ({ provider: providerURL, @@ -81,12 +78,18 @@ export const createProviderFromJsonConfig = ( ); } - const provider = new ConfiguredJsonRpcProvider( + // Create JsonRpcProvider + const provider = new JsonRpcProvider( providerURL, - network, - maxLogsPerBatch, + config.chainId, + { + staticNetwork: true, + pollingInterval, + batchMaxCount: maxLogsPerBatch, + }, ); + // Add JsonRpcProvider to FallbackProviderConfig const fallbackProviderConfig: FallbackProviderConfig = { provider, priority, @@ -97,7 +100,8 @@ export const createProviderFromJsonConfig = ( }, ); - return new FallbackProvider(providers, network, { + // Return FallbackProvider containing array of JsonRpcProviders + return new FallbackProvider(providers, config.chainId, { pollingInterval }); } catch (cause) { From f278c5cdbbb2fd2e0b17ad865259aabe7f5cd1fd Mon Sep 17 00:00:00 2001 From: germi Date: Wed, 29 Jan 2025 10:23:35 -0300 Subject: [PATCH 6/6] misc: prettier --- src/utils/available-rpc.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/available-rpc.ts b/src/utils/available-rpc.ts index 7d70b3b..39483f8 100644 --- a/src/utils/available-rpc.ts +++ b/src/utils/available-rpc.ts @@ -68,7 +68,7 @@ const getBlockNumber = async ( // Conditionally handle what type of provider is being passed let rpcProvider: Provider; - if(provider.startsWith('wss')) { + if (provider.startsWith('wss')) { rpcProvider = new WebSocketProvider(provider, network, { staticNetwork: network, // Network is dictated in the RPC URL, will not change });