Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 58 additions & 7 deletions src/utils/__tests__/available-rpc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -24,12 +25,12 @@ describe('available-rpc', () => {
],
};

const fallbackProvider = createFallbackProviderFromJsonConfig(config);
const fallbackProvider = createProviderFromJsonConfig(config);

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: [
Expand All @@ -42,11 +43,61 @@ describe('available-rpc', () => {
],
};

const fallbackProvider = createFallbackProviderFromJsonConfig(config);

await fallbackProvider.getBlockNumber();
try {
// This should throw an error because WSS is not supported in FallbackProvider
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(
"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 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);
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 {
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");
}
}
});

it('Should sort ascending and descending', () => {
const allConfigs = [
{
Expand Down
12 changes: 6 additions & 6 deletions src/utils/available-rpc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference types="../types/global" />
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';

Expand Down Expand Up @@ -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);
if (provider.startsWith('wss')) {
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
});
}

Expand Down
74 changes: 0 additions & 74 deletions src/utils/fallback-provider.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
117 changes: 117 additions & 0 deletions src/utils/provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { FallbackProvider, JsonRpcProvider, WebSocketProvider } from 'ethers';
import { FallbackProviderConfig } from 'ethers/lib.commonjs/providers/provider-fallback';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've did not notice we were importing this type from the internal structure of ethers!, given that any update on this might be a breaking change for us, is there any better way we can handle this type ? Mirroring this type with an interface of ours probably ?? or inferring the type.. idk 🤣

import { isDefined } from './util';

export type FallbackProviderJsonConfig = {
chainId: number;
providers: ProviderJson[];
};

export type ProviderJson = {
priority: number;
weight: number;
provider: string;
chainId?: number;
stallTimeout?: number;
maxLogsPerBatch?: number;
};

export const createProviderFromJsonConfig = (
config: FallbackProviderJsonConfig | ProviderJson,
pollingInterval?: number,
): FallbackProvider | WebSocketProvider | JsonRpcProvider => {
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');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as a reminder, lets try to make this errors look similar to #38

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you provide an example? Not clear what change is being asked for

} 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');
}

// 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,
);
if (totalWeight < 2) {
throw new Error(
'Total weight across providers must be >= 2 for fallback quorum.',
);
}

const providers: FallbackProviderConfig[] = config.providers.map(
({
provider: providerURL,
priority,
weight,
stallTimeout,
maxLogsPerBatch,
}) => {
const isWebsocket = providerURL.startsWith('wss');
if (isWebsocket) {
throw new Error(
'WebSocketProvider not supported in FallbackProvider as it will use polling instead of eth_subscribe',
);
}

// Create JsonRpcProvider
const provider = new JsonRpcProvider(
providerURL,
config.chainId,
{
staticNetwork: true,
pollingInterval,
batchMaxCount: maxLogsPerBatch,
},
);

// Add JsonRpcProvider to FallbackProviderConfig
const fallbackProviderConfig: FallbackProviderConfig = {
provider,
priority,
weight,
stallTimeout,
};
return fallbackProviderConfig;
},
);

// Return FallbackProvider containing array of JsonRpcProviders
return new FallbackProvider(providers, config.chainId, {
pollingInterval
});
} catch (cause) {
if (!(cause instanceof Error)) {
throw new Error(
'Non-error thrown from createFallbackProviderFromJsonConfig',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might this be a typo!? this error message is createFallbackProviderFromJsonConfig, but the function is named createProviderFromJsonConfig, is this intended ?

{ cause },
);
}
// Preserve the original error message
throw cause;
}
};