Skip to content

Commit

Permalink
Web3 RPC Providers support of configuration of selected transport (#7205
Browse files Browse the repository at this point in the history
)

* SocketOptions type

* ProviderConfigOptionsError

* providerConfigOptions

* tests

* changelog
  • Loading branch information
jdevcs authored Aug 21, 2024
1 parent d9d0391 commit 2ef694c
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 27 deletions.
3 changes: 2 additions & 1 deletion packages/web3-rpc-providers/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Updated rate limit error of QuickNode provider for HTTP transport
- Updated rate limit error of QuickNode provider for HTTP transport
- Added optional `HttpProviderOptions | SocketOptions` in `Web3ExternalProvider` and `QuickNodeProvider` for provider configs
14 changes: 13 additions & 1 deletion packages/web3-rpc-providers/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/

/* eslint-disable max-classes-per-file */

import { BaseWeb3Error } from 'web3-errors';

const ERR_QUICK_NODE_RATE_LIMIT = 1300;
Expand All @@ -24,4 +26,14 @@ export class QuickNodeRateLimitError extends BaseWeb3Error {
public constructor(error?: Error) {
super(`You've reach the rate limit of free RPC calls from our Partner Quick Nodes. There are two options you can either create a paid Quick Nodes account and get 20% off for 2 months using WEB3JS referral code, or use Free public RPC endpoint.`, error);
}
}
}

const ERR_PROVIDER_CONFIG_OPTIONS = 1301;
export class ProviderConfigOptionsError extends BaseWeb3Error {
public code = ERR_PROVIDER_CONFIG_OPTIONS;

public constructor(msg: string) {
super(`Invalid provider config options given for ${msg}`);
}
}
/* eslint-enable max-classes-per-file */
9 changes: 9 additions & 0 deletions packages/web3-rpc-providers/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/

import {ClientOptions, ClientRequestArgs} from "web3-providers-ws";
import { ReconnectOptions } from 'web3-utils';

export enum Transport {
HTTPS = "https",
WebSocket = "wss"
Expand All @@ -41,4 +44,10 @@ export enum Network {

BNB_MAINNET = "bnb_mainnet",
BNB_TESTNET = "bnb_testnet"
};

// Combining the ws types
export type SocketOptions = {
socketOptions?: ClientOptions | ClientRequestArgs;
reconnectOptions?: Partial<ReconnectOptions>;
};
34 changes: 28 additions & 6 deletions packages/web3-rpc-providers/src/web3_provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/

import HttpProvider from "web3-providers-http";
import HttpProvider, { HttpProviderOptions } from "web3-providers-http";
import WebSocketProvider from "web3-providers-ws";
import {
EthExecutionAPI, JsonRpcResult, ProviderConnectInfo, ProviderMessage,
Expand All @@ -27,7 +27,8 @@ import {
JsonRpcResponseWithResult,
} from "web3-types";
import { Eip1193Provider } from "web3-utils";
import { Transport, Network } from "./types.js";
import { Transport, Network, SocketOptions } from "./types.js";
import { ProviderConfigOptionsError } from "./errors.js";

/*
This class can be used to create new providers only when there is custom logic required in each Request method like
Expand All @@ -50,16 +51,36 @@ export abstract class Web3ExternalProvider<
network: Network,
transport: Transport,
token: string,
host: string) {
host: string,
providerConfigOptions?: HttpProviderOptions | SocketOptions) {

super();

if(providerConfigOptions!== undefined &&
transport === Transport.HTTPS &&
!('providerOptions' in providerConfigOptions)){

throw new ProviderConfigOptionsError("HTTP Provider");
}
else if(providerConfigOptions!== undefined &&
transport === Transport.WebSocket &&
!( 'socketOptions' in providerConfigOptions ||
'reconnectOptions' in providerConfigOptions
)){
throw new ProviderConfigOptionsError("Websocket Provider");
}

this.transport = transport;
if (transport === Transport.HTTPS) {
this.provider = new HttpProvider(this.getRPCURL(network, transport, token, host));
this.provider = new HttpProvider(
this.getRPCURL(network, transport, token, host),
providerConfigOptions as HttpProviderOptions);
}
else if (transport === Transport.WebSocket) {
this.provider = new WebSocketProvider(this.getRPCURL(network, transport, token, host));
this.provider = new WebSocketProvider(
this.getRPCURL(network, transport, token, host),
(providerConfigOptions as SocketOptions)?.socketOptions,
(providerConfigOptions as SocketOptions)?.reconnectOptions);
}
}

Expand Down Expand Up @@ -133,4 +154,5 @@ export abstract class Web3ExternalProvider<
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
this.provider.removeListener(_type as any, _listener as any);
}
}
}

12 changes: 5 additions & 7 deletions packages/web3-rpc-providers/src/web3_provider_quicknode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.

import { EthExecutionAPI, JsonRpcResponseWithResult, Web3APIMethod, Web3APIPayload, Web3APIReturnType, Web3APISpec } from "web3-types";
import { ResponseError } from "web3-errors";
import { Transport, Network } from "./types.js";
import { HttpProviderOptions } from "web3-providers-http";
import { Transport, Network, SocketOptions } from "./types.js";
import { Web3ExternalProvider } from "./web3_provider.js";
import { QuickNodeRateLimitError } from "./errors.js";

Expand All @@ -27,13 +28,10 @@ export class QuickNodeProvider<
API extends Web3APISpec = EthExecutionAPI,
> extends Web3ExternalProvider {

public constructor(
network: Network = Network.ETH_MAINNET,
transport: Transport = Transport.HTTPS,
token = "",
host = "") {
// eslint-disable-next-line default-param-last
public constructor( network: Network = Network.ETH_MAINNET, transport: Transport = Transport.HTTPS, token = "", host = "", providerConfigOptions?: HttpProviderOptions | SocketOptions) {

super(network, transport, token, host);
super(network, transport, token, host, providerConfigOptions);

}

Expand Down
87 changes: 75 additions & 12 deletions packages/web3-rpc-providers/test/unit/constructor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/


import HttpProvider from 'web3-providers-http';
import HttpProvider, { HttpProviderOptions } from 'web3-providers-http';
import WebSocketProvider from 'web3-providers-ws';
import WebSocket from 'isomorphic-ws';

import { Web3ExternalProvider } from '../../src/web3_provider';
import { Network, Transport } from '../../src/types';
import { Network, SocketOptions, Transport } from '../../src/types';
import { ProviderConfigOptionsError } from '../../src/errors';

// Mock implementation so ws doesnt have openhandle after test exits as it attempts to connects at start
jest.mock('isomorphic-ws', () => {
Expand Down Expand Up @@ -60,11 +61,11 @@ jest.mock('isomorphic-ws', () => {
});

class MockWeb3ExternalProviderA extends Web3ExternalProvider {
public constructor(network: Network, transport: Transport, token: string){
super(network, transport, token, "");
public constructor(network: Network, transport: Transport, token: string, host?: string, providerConfigOptions?: HttpProviderOptions | SocketOptions) {
super(network, transport, token, host ?? "", providerConfigOptions);
}
// eslint-disable-next-line class-methods-use-this
public getRPCURL(_network: Network, _transport: Transport, _token: string, _host=""): string {
public getRPCURL(_network: Network, _transport: Transport, _token: string, _host = ""): string {
let transport = "";
if (_transport === Transport.HTTPS)
transport = "http://";
Expand All @@ -76,23 +77,85 @@ class MockWeb3ExternalProviderA extends Web3ExternalProvider {
}

describe('Web3ExternalProvider', () => {
const network: Network = Network.ETH_MAINNET;
const transport: Transport = Transport.HTTPS;
const token = 'test-token';
const host = 'test-host';

it('should initialize the provider correctly', () => {
const network: Network = Network.ETH_MAINNET;
const transport: Transport = Transport.HTTPS;
const token = 'your-token';

const provider = new MockWeb3ExternalProviderA(network, transport, token);

expect(provider.provider).toBeInstanceOf(HttpProvider);
});

it('should initialize the provider with WebSocketProvider for WebSocket transport', () => {
const network: Network = Network.ETH_MAINNET;
const transport: Transport = Transport.WebSocket;
const token = 'your-token';
const transport1: Transport = Transport.WebSocket;

const provider = new MockWeb3ExternalProviderA(network, transport, token);
const provider = new MockWeb3ExternalProviderA(network, transport1, token);
expect(provider.provider).toBeInstanceOf(WebSocketProvider);
});

it('should throw ProviderConfigOptionsError for HTTP provider with missing providerOptions', () => {
const providerConfigOptions: HttpProviderOptions | SocketOptions = { /* missing providerOptions */ };
expect(() => new MockWeb3ExternalProviderA(network, transport, token, host, providerConfigOptions)).toThrow(ProviderConfigOptionsError);
});

it('should throw ProviderConfigOptionsError for HTTP provider with WS providerOptions', () => {
const providerConfigOptions: SocketOptions = {
socketOptions: { /* options */ },
reconnectOptions: { /* options */ },
};
expect(() => new MockWeb3ExternalProviderA(network, transport, token, host, providerConfigOptions)).toThrow(ProviderConfigOptionsError);
});

it('should throw ProviderConfigOptionsError for WebSocket provider with missing socketOptions and reconnectOptions', () => {
const providerConfigOptions: HttpProviderOptions | SocketOptions = { /* missing socketOptions and reconnectOptions */ };
expect(() => new MockWeb3ExternalProviderA(network, Transport.WebSocket, token, host, providerConfigOptions)).toThrow(ProviderConfigOptionsError);
});

it('should throw ProviderConfigOptionsError for WebSocket provider with HTTP options', () => {
const providerConfigOptions: HttpProviderOptions = { providerOptions: { /* options */ } };
expect(() => new MockWeb3ExternalProviderA(network, Transport.WebSocket, token, host, providerConfigOptions)).toThrow(ProviderConfigOptionsError);
});

it('should create provider instance and not throw ProviderConfigOptionsError for WebSocket provider with missing reconnectOptions', () => {
const providerConfigOptions: SocketOptions = {
socketOptions: { /* options */ },
};

// Create an instance of the MockWeb3ExternalProviderA
const provider = new MockWeb3ExternalProviderA(network, Transport.WebSocket, token, host, providerConfigOptions);

// Expect that the provider is created successfully
expect(provider).toBeInstanceOf(MockWeb3ExternalProviderA);
});

it('should create provider instance and not throw ProviderConfigOptionsError for WebSocket provider with missing socketOptions', () => {
const providerConfigOptions: SocketOptions = {
reconnectOptions: { /* options */ },
};

// Create an instance of the MockWeb3ExternalProviderA
const provider = new MockWeb3ExternalProviderA(network, Transport.WebSocket, token, host, providerConfigOptions);

// Expect that the provider is created successfully
expect(provider).toBeInstanceOf(MockWeb3ExternalProviderA);
});

it('should create an HttpProvider with providerOptions', () => {
const providerConfigOptions: HttpProviderOptions = { providerOptions: { /* options */ } };
const provider = new MockWeb3ExternalProviderA(network, transport, token, host, providerConfigOptions);
expect(provider.provider).toBeInstanceOf(HttpProvider);
});

it('should create a WebSocketProvider with socketOptions and reconnectOptions', () => {
const providerConfigOptions: SocketOptions = {
socketOptions: { /* options */ },
reconnectOptions: { /* options */ },
};
const provider = new MockWeb3ExternalProviderA(network, Transport.WebSocket, token, host, providerConfigOptions);
expect(provider.provider).toBeInstanceOf(WebSocketProvider);
});
});

1 comment on commit 2ef694c

@github-actions
Copy link

Choose a reason for hiding this comment

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

Benchmark

Benchmark suite Current: 2ef694c Previous: d9d0391 Ratio
processingTx 21525 ops/sec (±8.39%) 21240 ops/sec (±7.90%) 0.99
processingContractDeploy 39477 ops/sec (±5.57%) 38066 ops/sec (±7.88%) 0.96
processingContractMethodSend 15159 ops/sec (±10.70%) 15268 ops/sec (±7.65%) 1.01
processingContractMethodCall 28527 ops/sec (±6.08%) 27168 ops/sec (±7.01%) 0.95
abiEncode 43796 ops/sec (±6.74%) 41584 ops/sec (±6.92%) 0.95
abiDecode 29645 ops/sec (±8.38%) 29253 ops/sec (±7.27%) 0.99
sign 1515 ops/sec (±3.35%) 1493 ops/sec (±1.31%) 0.99
verify 371 ops/sec (±0.43%) 360 ops/sec (±0.75%) 0.97

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.