Skip to content

Commit

Permalink
Added initial throttling support (#139, #904, #926).
Browse files Browse the repository at this point in the history
  • Loading branch information
ricmoo committed Jul 14, 2020
1 parent c730cbc commit 88c7eae
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 62 deletions.
15 changes: 12 additions & 3 deletions packages/providers/src.ts/alchemy-provider.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"use strict";

import { Network, Networkish } from "@ethersproject/networks";
import { ConnectionInfo } from "@ethersproject/web";

import { showThrottleMessage } from "./formatter";
import { WebSocketProvider } from "./websocket-provider";

import { Logger } from "@ethersproject/logger";
Expand All @@ -18,7 +20,6 @@ import { UrlJsonRpcProvider } from "./url-json-rpc-provider";
const defaultApiKey = "_gg7wSSi0KMBsdKnGVfHDueq6xMB9EkC"

export class AlchemyProvider extends UrlJsonRpcProvider {
readonly apiKey: string;

static getWebSocketProvider(network?: Networkish, apiKey?: any): WebSocketProvider {
const provider = new AlchemyProvider(network, apiKey);
Expand All @@ -37,7 +38,7 @@ export class AlchemyProvider extends UrlJsonRpcProvider {
return apiKey;
}

static getUrl(network: Network, apiKey: string): string {
static getUrl(network: Network, apiKey: string): ConnectionInfo {
let host = null;
switch (network.name) {
case "homestead":
Expand All @@ -59,6 +60,14 @@ export class AlchemyProvider extends UrlJsonRpcProvider {
logger.throwArgumentError("unsupported network", "network", arguments[0]);
}

return ("https:/" + "/" + host + apiKey);
return {
url: ("https:/" + "/" + host + apiKey),
throttleCallback: (attempt: number, url: string) => {
if (apiKey === defaultApiKey) {
showThrottleMessage();
}
return Promise.resolve(true);
}
};
}
}
44 changes: 39 additions & 5 deletions packages/providers/src.ts/etherscan-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { Network, Networkish } from "@ethersproject/networks";
import { deepCopy, defineReadOnly } from "@ethersproject/properties";
import { fetchJson } from "@ethersproject/web";

import { showThrottleMessage } from "./formatter";

import { Logger } from "@ethersproject/logger";
import { version } from "./_version";
const logger = new Logger(version);
Expand Down Expand Up @@ -34,16 +36,26 @@ function getResult(result: { status?: number, message?: string, result?: any }):
}

if (result.status != 1 || result.message != "OK") {
// @TODO: not any
const error: any = new Error("invalid response");
error.result = JSON.stringify(result);
if ((result.result || "").toLowerCase().indexOf("rate limit") >= 0) {
error.throttleRetry = true;
}
throw error;
}

return result.result;
}

function getJsonResult(result: { jsonrpc: string, result?: any, error?: { code?: number, data?: any, message?: string} } ): any {
// This response indicates we are being throttled
if (result && (<any>result).status == 0 && (<any>result).message == "NOTOK" && (result.result || "").toLowerCase().indexOf("rate limit") >= 0) {
const error: any = new Error("throttled response");
error.result = JSON.stringify(result);
error.throttleRetry = true;
throw error;
}

if (result.jsonrpc != "2.0") {
// @TODO: not any
const error: any = new Error("invalid response");
Expand Down Expand Up @@ -76,6 +88,7 @@ const defaultApiKey = "9D13ZE7XSBTJ94N9BNJ2MA33VMAY2YPIRB";
export class EtherscanProvider extends BaseProvider{
readonly baseUrl: string;
readonly apiKey: string;

constructor(network?: Networkish, apiKey?: string) {
logger.checkNew(new.target, EtherscanProvider);

Expand Down Expand Up @@ -126,7 +139,18 @@ export class EtherscanProvider extends BaseProvider{
provider: this
});

const result = await fetchJson(url, null, procFunc || getJsonResult);

const connection = {
url: url,
throttleCallback: (attempt: number, url: string) => {
if (this.apiKey === defaultApiKey) {
showThrottleMessage();
}
return Promise.resolve(true);
}
};

const result = await fetchJson(connection, null, procFunc || getJsonResult);

this.emit("debug", {
action: "response",
Expand Down Expand Up @@ -162,13 +186,13 @@ export class EtherscanProvider extends BaseProvider{
case "getCode":
url += "/api?module=proxy&action=eth_getCode&address=" + params.address;
url += "&tag=" + params.blockTag + apiKey;
return get(url, getJsonResult);
return get(url);

case "getStorageAt":
url += "/api?module=proxy&action=eth_getStorageAt&address=" + params.address;
url += "&position=" + params.position;
url += "&tag=" + params.blockTag + apiKey;
return get(url, getJsonResult);
return get(url);


case "sendTransaction":
Expand Down Expand Up @@ -325,7 +349,17 @@ export class EtherscanProvider extends BaseProvider{
provider: this
});

return fetchJson(url, null, getResult).then((result: Array<any>) => {
const connection = {
url: url,
throttleCallback: (attempt: number, url: string) => {
if (this.apiKey === defaultApiKey) {
showThrottleMessage();
}
return Promise.resolve(true);
}
}

return fetchJson(connection, null, getResult).then((result: Array<any>) => {
this.emit("debug", {
action: "response",
request: url,
Expand Down
20 changes: 20 additions & 0 deletions packages/providers/src.ts/formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,3 +455,23 @@ export class Formatter {
}
}

// Show the throttle message only once
let throttleMessage = false;
export function showThrottleMessage() {
if (throttleMessage) { return; }
throttleMessage = true;

console.log("========= NOTICE =========")
console.log("Request-Rate Exceeded (this message will not be repeated)");
console.log("");
console.log("The default API keys for each service are provided as a highly-throttled,");
console.log("community resource for low-traffic projects and early prototyping.");
console.log("");
console.log("While your application will continue to function, we highly recommended");
console.log("signing up for your own API keys to improve performance, increase your");
console.log("request rate/limit and enable other perks, such as metrics and advanced APIs.");
console.log("");
console.log("For more details: https:/\/docs.ethers.io/api-keys/");
console.log("==========================");
}

11 changes: 9 additions & 2 deletions packages/providers/src.ts/infura-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Network, Networkish } from "@ethersproject/networks";
import { ConnectionInfo } from "@ethersproject/web";

import { WebSocketProvider } from "./websocket-provider";
import { showThrottleMessage } from "./formatter";

import { Logger } from "@ethersproject/logger";
import { version } from "./_version";
Expand Down Expand Up @@ -61,7 +62,7 @@ export class InfuraProvider extends UrlJsonRpcProvider {
return apiKeyObj;
}

static getUrl(network: Network, apiKey: any): string | ConnectionInfo {
static getUrl(network: Network, apiKey: any): ConnectionInfo {
let host: string = null;
switch(network ? network.name: "unknown") {
case "homestead":
Expand All @@ -87,7 +88,13 @@ export class InfuraProvider extends UrlJsonRpcProvider {
}

const connection: ConnectionInfo = {
url: ("https:/" + "/" + host + "/v3/" + apiKey.projectId)
url: ("https:/" + "/" + host + "/v3/" + apiKey.projectId),
throttleCallback: (attempt: number, url: string) => {
if (apiKey.projectId === defaultProjectId) {
showThrottleMessage();
}
return Promise.resolve(true);
}
};

if (apiKey.projectSecret != null) {
Expand Down
2 changes: 2 additions & 0 deletions packages/providers/src.ts/nodesmith-provider.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* istanbul ignore file */

"use strict";

import { Network } from "@ethersproject/networks";
Expand Down
Loading

0 comments on commit 88c7eae

Please sign in to comment.