Allow providers to detect their network after instantiation (#814).

This commit is contained in:
Richard Moore 2020-05-03 17:32:16 -04:00
parent 0e3a66c829
commit 99ae946476
No known key found for this signature in database
GPG Key ID: 665176BE8E9DC651
5 changed files with 82 additions and 41 deletions

@ -175,6 +175,7 @@ let nextPollId = 1;
export class BaseProvider extends Provider { export class BaseProvider extends Provider {
_networkPromise: Promise<Network>;
_network: Network; _network: Network;
_events: Array<Event>; _events: Array<Event>;
@ -215,7 +216,6 @@ export class BaseProvider extends Provider {
* MUST set this. Standard named networks have a known chainId. * MUST set this. Standard named networks have a known chainId.
* *
*/ */
ready: Promise<Network>;
constructor(network: Networkish | Promise<Network>) { constructor(network: Networkish | Promise<Network>) {
logger.checkNew(new.target, Provider); logger.checkNew(new.target, Provider);
@ -225,19 +225,15 @@ export class BaseProvider extends Provider {
this.formatter = new.target.getFormatter(); this.formatter = new.target.getFormatter();
if (network instanceof Promise) { if (network instanceof Promise) {
defineReadOnly(this, "ready", network.then((network) => { this._networkPromise = network;
defineReadOnly(this, "_network", network);
return network;
}));
// Squash any "unhandled promise" errors; that do not need to be handled // Squash any "unhandled promise" errors; that do not need to be handled
this.ready.catch((error) => { }); network.catch((error) => { });
} else { } else {
const knownNetwork = getStatic<(network: Networkish) => Network>(new.target, "getNetwork")(network); const knownNetwork = getStatic<(network: Networkish) => Network>(new.target, "getNetwork")(network);
if (knownNetwork) { if (knownNetwork) {
defineReadOnly(this, "_network", knownNetwork); defineReadOnly(this, "_network", knownNetwork);
defineReadOnly(this, "ready", Promise.resolve(this._network));
} else { } else {
logger.throwArgumentError("invalid network", "network", network); logger.throwArgumentError("invalid network", "network", network);
@ -258,6 +254,42 @@ export class BaseProvider extends Provider {
this._fastQueryDate = 0; this._fastQueryDate = 0;
} }
async _ready(): Promise<Network> {
if (this._network == null) {
let network: Network = null;
if (this._networkPromise) {
try {
network = await this._networkPromise;
} catch (error) { }
}
// Try the Provider's network detection (this MUST throw if it cannot)
if (network == null) {
network = await this.detectNetwork();
}
// This should never happen; every Provider sub-class should have
// suggested a network by here (or thrown).
if (!network) {
logger.throwError("no network detected", Logger.errors.UNKNOWN_ERROR, { });
}
defineReadOnly(this, "_network", network);
}
return this._network;
}
get ready(): Promise<Network> {
return this._ready();
}
async detectNetwork(): Promise<Network> {
return logger.throwError("provider does not support network detection", Logger.errors.UNSUPPORTED_OPERATION, {
operation: "provider.detectNetwork"
});
}
static getFormatter(): Formatter { static getFormatter(): Formatter {
if (defaultFormatter == null) { if (defaultFormatter == null) {
defaultFormatter = new Formatter(); defaultFormatter = new Formatter();

@ -2,7 +2,7 @@
import { BlockTag, TransactionRequest, TransactionResponse } from "@ethersproject/abstract-provider"; import { BlockTag, TransactionRequest, TransactionResponse } from "@ethersproject/abstract-provider";
import { hexlify, hexValue } from "@ethersproject/bytes"; import { hexlify, hexValue } from "@ethersproject/bytes";
import { Networkish } from "@ethersproject/networks"; import { Network, Networkish } from "@ethersproject/networks";
import { deepCopy, defineReadOnly } from "@ethersproject/properties"; import { deepCopy, defineReadOnly } from "@ethersproject/properties";
import { fetchJson } from "@ethersproject/web"; import { fetchJson } from "@ethersproject/web";
@ -109,6 +109,9 @@ export class EtherscanProvider extends BaseProvider{
defineReadOnly(this, "apiKey", apiKey || defaultApiKey); defineReadOnly(this, "apiKey", apiKey || defaultApiKey);
} }
async detectNetwork(): Promise<Network> {
return this.network;
}
async perform(method: string, params: any): Promise<any> { async perform(method: string, params: any): Promise<any> {
let url = this.baseUrl; let url = this.baseUrl;

@ -379,14 +379,8 @@ export class FallbackProvider extends BaseProvider {
const network = checkNetworks(providerConfigs.map((c) => (<any>(c.provider)).network)); const network = checkNetworks(providerConfigs.map((c) => (<any>(c.provider)).network));
if (network) { if (network) {
super(network); super(network);
} else { } else {
// The network won't be known until all child providers know super(this.detectNetwork());
const ready = Promise.all(providerConfigs.map((c) => c.provider.getNetwork())).then((networks) => {
return checkNetworks(networks);
});
super(ready);
} }
// Preserve a copy, so we do not get mutated // Preserve a copy, so we do not get mutated
@ -396,6 +390,11 @@ export class FallbackProvider extends BaseProvider {
this._highestBlockNumber = -1; this._highestBlockNumber = -1;
} }
async detectNetwork(): Promise<Network> {
const networks = await Promise.all(this.providerConfigs.map((c) => c.provider.getNetwork()));
return checkNetworks(networks);
}
async perform(method: string, params: { [name: string]: any }): Promise<any> { async perform(method: string, params: { [name: string]: any }): Promise<any> {
// Sending transactions is special; always broadcast it to all backends // Sending transactions is special; always broadcast it to all backends

@ -20,9 +20,7 @@ import { BaseProvider, Event } from "./base-provider";
function timer(timeout: number): Promise<any> { function timer(timeout: number): Promise<any> {
return new Promise(function(resolve) { return new Promise(function(resolve) {
setTimeout(function() { setTimeout(resolve, timeout);
resolve();
}, timeout);
}); });
} }
@ -235,31 +233,9 @@ export class JsonRpcProvider extends BaseProvider {
if (network) { if (network) {
// The network has been specified explicitly, we can use it // The network has been specified explicitly, we can use it
super(network); super(network);
} else { } else {
// The network is unknown, query the JSON-RPC for it // The network is unknown, query the JSON-RPC for it
const ready: Promise<Network> = new Promise((resolve, reject) => { super(this.detectNetwork());
setTimeout(async () => {
let chainId = null;
try {
chainId = await this.send("eth_chainId", [ ]);
} catch (error) {
try {
chainId = await this.send("net_version", [ ]);
} catch (error) { }
}
if (chainId != null) {
try {
return resolve(getNetwork(BigNumber.from(chainId).toNumber()));
} catch (error) { }
}
reject(logger.makeError("could not detect network", Logger.errors.NETWORK_ERROR));
}, 0);
});
super(ready);
} }
// Default URL // Default URL
@ -280,6 +256,33 @@ export class JsonRpcProvider extends BaseProvider {
return "http:/\/localhost:8545"; return "http:/\/localhost:8545";
} }
async detectNetwork(): Promise<Network> {
await timer(0);
let chainId = null;
try {
chainId = await this.send("eth_chainId", [ ]);
} catch (error) {
try {
chainId = await this.send("net_version", [ ]);
} catch (error) { }
}
if (chainId != null) {
const getNetwork = getStatic<(network: Networkish) => Network>(this.constructor, "getNetwork");
try {
return getNetwork(BigNumber.from(chainId).toNumber());
} catch (error) {
return logger.throwError("could not detect network", Logger.errors.NETWORK_ERROR, {
chainId: chainId,
serverError: error
});
}
}
return logger.throwError("could not detect network", Logger.errors.NETWORK_ERROR);
}
getSigner(addressOrIndex?: string | number): JsonRpcSigner { getSigner(addressOrIndex?: string | number): JsonRpcSigner {
return new JsonRpcSigner(_constructorGuard, this, addressOrIndex); return new JsonRpcSigner(_constructorGuard, this, addressOrIndex);
} }

@ -36,6 +36,10 @@ export abstract class UrlJsonRpcProvider extends JsonRpcProvider {
} }
} }
async detectNetwork(): Promise<Network> {
return this.network;
}
_startPending(): void { _startPending(): void {
logger.warn("WARNING: API provider does not support pending filters"); logger.warn("WARNING: API provider does not support pending filters");
} }