From 4681b83d516ab2eb41ddb68b5021c97e14c6f2cf Mon Sep 17 00:00:00 2001 From: Richard Moore Date: Mon, 27 Nov 2023 05:23:10 -0500 Subject: [PATCH] Add auto-detected static network support to providers and allow customizing socket provider options (#4199, #4418, #4441). --- src.ts/providers/provider-ipcsocket.ts | 6 ++- src.ts/providers/provider-jsonrpc.ts | 70 ++++++++++++++++++-------- src.ts/providers/provider-socket.ts | 23 +++++++-- src.ts/providers/provider-websocket.ts | 5 +- 4 files changed, 76 insertions(+), 28 deletions(-) diff --git a/src.ts/providers/provider-ipcsocket.ts b/src.ts/providers/provider-ipcsocket.ts index 6226f658be..2304bfbae3 100644 --- a/src.ts/providers/provider-ipcsocket.ts +++ b/src.ts/providers/provider-ipcsocket.ts @@ -3,6 +3,8 @@ import { connect } from "net"; import { SocketProvider } from "./provider-socket.js"; import type { Socket } from "net"; + +import type { JsonRpcApiProviderOptions } from "./provider-jsonrpc.js"; import type { Networkish } from "./network.js"; @@ -35,8 +37,8 @@ export class IpcSocketProvider extends SocketProvider { */ get socket(): Socket { return this.#socket; } - constructor(path: string, network?: Networkish) { - super(network); + constructor(path: string, network?: Networkish, options?: JsonRpcApiProviderOptions) { + super(network, options); this.#socket = connect(path); this.socket.on("ready", async () => { diff --git a/src.ts/providers/provider-jsonrpc.ts b/src.ts/providers/provider-jsonrpc.ts index 4069ffb1e1..8674964e43 100644 --- a/src.ts/providers/provider-jsonrpc.ts +++ b/src.ts/providers/provider-jsonrpc.ts @@ -190,7 +190,7 @@ export type DebugEventJsonRpcApiProvider = { */ export type JsonRpcApiProviderOptions = { polling?: boolean; - staticNetwork?: null | Network; + staticNetwork?: null | boolean | Network; batchStallTime?: number; batchMaxSize?: number; batchMaxCount?: number; @@ -463,6 +463,7 @@ export abstract class JsonRpcApiProvider extends AbstractProvider { }; #network: null | Network; + #pendingDetectNetwork: null | Promise; #scheduleDrain(): void { if (this.#drainTimer) { return; } @@ -554,6 +555,7 @@ export abstract class JsonRpcApiProvider extends AbstractProvider { this.#drainTimer = null; this.#network = null; + this.#pendingDetectNetwork = null; { let resolve: null | ((value: void) => void) = null; @@ -563,9 +565,15 @@ export abstract class JsonRpcApiProvider extends AbstractProvider { this.#notReady = { promise, resolve }; } - // Make sure any static network is compatbile with the provided netwrok const staticNetwork = this._getOption("staticNetwork"); - if (staticNetwork) { + if (typeof(staticNetwork) === "boolean") { + assertArgument(!staticNetwork || network !== "any", "staticNetwork cannot be used on special network 'any'", "options", options); + if (staticNetwork && network != null) { + this.#network = Network.from(network); + } + + } else if (staticNetwork) { + // Make sure any static network is compatbile with the provided netwrok assertArgument(network == null || staticNetwork.matches(network), "staticNetwork MUST match network object", "options", options); this.#network = staticNetwork; @@ -641,36 +649,56 @@ export abstract class JsonRpcApiProvider extends AbstractProvider { */ async _detectNetwork(): Promise { const network = this._getOption("staticNetwork"); - if (network) { return network; } + if (network) { + if (network === true) { + if (this.#network) { return this.#network; } + } else { + return network; + } + } + + if (this.#pendingDetectNetwork) { + return await this.#pendingDetectNetwork; + } // If we are ready, use ``send``, which enabled requests to be batched if (this.ready) { - return Network.from(getBigInt(await this.send("eth_chainId", [ ]))); + this.#pendingDetectNetwork = (async () => { + const result = Network.from(getBigInt(await this.send("eth_chainId", [ ]))); + this.#pendingDetectNetwork = null; + return result; + })(); + return await this.#pendingDetectNetwork; } // We are not ready yet; use the primitive _send + this.#pendingDetectNetwork = (async () => { + const payload: JsonRpcPayload = { + id: this.#nextId++, method: "eth_chainId", params: [ ], jsonrpc: "2.0" + }; - const payload: JsonRpcPayload = { - id: this.#nextId++, method: "eth_chainId", params: [ ], jsonrpc: "2.0" - }; + this.emit("debug", { action: "sendRpcPayload", payload }); - this.emit("debug", { action: "sendRpcPayload", payload }); + let result: JsonRpcResult | JsonRpcError; + try { + result = (await this._send(payload))[0]; + this.#pendingDetectNetwork = null; + } catch (error) { + this.#pendingDetectNetwork = null; + this.emit("debug", { action: "receiveRpcError", error }); + throw error; + } - let result: JsonRpcResult | JsonRpcError; - try { - result = (await this._send(payload))[0]; - } catch (error) { - this.emit("debug", { action: "receiveRpcError", error }); - throw error; - } + this.emit("debug", { action: "receiveRpcResult", result }); - this.emit("debug", { action: "receiveRpcResult", result }); + if ("result" in result) { + return Network.from(getBigInt(result.result)); + } - if ("result" in result) { - return Network.from(getBigInt(result.result)); - } + throw this.getRpcError(payload, result); + })(); - throw this.getRpcError(payload, result); + return await this.#pendingDetectNetwork; } /** diff --git a/src.ts/providers/provider-socket.ts b/src.ts/providers/provider-socket.ts index 88c0749199..e5ec69f4e2 100644 --- a/src.ts/providers/provider-socket.ts +++ b/src.ts/providers/provider-socket.ts @@ -15,7 +15,9 @@ import { JsonRpcApiProvider } from "./provider-jsonrpc.js"; import type { Subscriber, Subscription } from "./abstract-provider.js"; import type { EventFilter } from "./provider.js"; -import type { JsonRpcError, JsonRpcPayload, JsonRpcResult } from "./provider-jsonrpc.js"; +import type { + JsonRpcApiProviderOptions, JsonRpcError, JsonRpcPayload, JsonRpcResult +} from "./provider-jsonrpc.js"; import type { Networkish } from "./network.js"; @@ -194,8 +196,23 @@ export class SocketProvider extends JsonRpcApiProvider { * * If unspecified, the network will be discovered. */ - constructor(network?: Networkish) { - super(network, { batchMaxCount: 1 }); + constructor(network?: Networkish, _options?: JsonRpcApiProviderOptions) { + // Copy the options + const options = Object.assign({ }, (_options != null) ? _options: { }); + + // Support for batches is generally not supported for + // connection-base providers; if this changes in the future + // the _send should be updated to reflect this + assertArgument(options.batchMaxCount == null || options.batchMaxCount === 1, + "sockets-based providers do not support batches", "options.batchMaxCount", _options); + options.batchMaxCount = 1; + + // Socket-based Providers (generally) cannot change their network, + // since they have a long-lived connection; but let people override + // this if they have just cause. + if (options.staticNetwork == null) { options.staticNetwork = true; } + + super(network, options); this.#callbacks = new Map(); this.#subs = new Map(); this.#pending = new Map(); diff --git a/src.ts/providers/provider-websocket.ts b/src.ts/providers/provider-websocket.ts index ea2ba51755..ee1cf847fa 100644 --- a/src.ts/providers/provider-websocket.ts +++ b/src.ts/providers/provider-websocket.ts @@ -4,6 +4,7 @@ import { WebSocket as _WebSocket } from "./ws.js"; /*-browser*/ import { SocketProvider } from "./provider-socket.js"; +import type { JsonRpcApiProviderOptions} from "./provider-jsonrpc.js"; import type { Networkish } from "./network.js"; /** @@ -45,8 +46,8 @@ export class WebSocketProvider extends SocketProvider { return this.#websocket; } - constructor(url: string | WebSocketLike | WebSocketCreator, network?: Networkish) { - super(network); + constructor(url: string | WebSocketLike | WebSocketCreator, network?: Networkish, options?: JsonRpcApiProviderOptions) { + super(network, options); if (typeof(url) === "string") { this.#connect = () => { return new _WebSocket(url); }; this.#websocket = this.#connect();