hardhat
Version:
Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.
161 lines • 7.09 kB
JavaScript
import { HardhatError } from "@nomicfoundation/hardhat-errors";
import { ensureError } from "@nomicfoundation/hardhat-utils/error";
import { sleep, isObject } from "@nomicfoundation/hardhat-utils/lang";
import { getDispatcher, getProxyUrl, isValidUrl, postJsonRequest, shouldUseProxy, ConnectionRefusedError, RequestTimeoutError, ResponseStatusCodeError, } from "@nomicfoundation/hardhat-utils/request";
import { EDR_NETWORK_REVERT_SNAPSHOT_EVENT } from "../../constants.js";
import { getHardhatVersion } from "../../utils/package.js";
import { BaseProvider } from "./base-provider.js";
import { getJsonRpcRequest, isFailedJsonRpcResponse, parseJsonRpcResponse, } from "./json-rpc.js";
import { ProviderError, LimitExceededError, UnknownError, } from "./provider-errors.js";
const TOO_MANY_REQUEST_STATUS = 429;
const MAX_RETRIES = 6;
const MAX_RETRY_WAIT_TIME_SECONDS = 5;
export class HttpProvider extends BaseProvider {
#url;
#networkName;
#extraHeaders;
#jsonRpcRequestWrapper;
#dispatcher;
#nextRequestId = 1;
/**
* Creates a new instance of `HttpProvider`.
*/
static async create({ url, networkName, extraHeaders = {}, timeout, jsonRpcRequestWrapper, testDispatcher, }) {
if (!isValidUrl(url)) {
throw new HardhatError(HardhatError.ERRORS.CORE.NETWORK.INVALID_URL, {
value: url,
});
}
const dispatcher = testDispatcher ?? (await getHttpDispatcher(url, timeout));
const httpProvider = new HttpProvider(url, networkName, extraHeaders, dispatcher, jsonRpcRequestWrapper);
return httpProvider;
}
/**
* @private
*
* This constructor is intended for internal use only.
* Use the static method {@link HttpProvider.create} to create an instance of
* `HttpProvider`.
*/
constructor(url, networkName, extraHeaders, dispatcher, jsonRpcRequestWrapper) {
super();
this.#url = url;
this.#networkName = networkName;
this.#extraHeaders = extraHeaders;
this.#dispatcher = dispatcher;
this.#jsonRpcRequestWrapper = jsonRpcRequestWrapper;
}
async request(requestArguments) {
if (this.#dispatcher === undefined) {
throw new HardhatError(HardhatError.ERRORS.CORE.NETWORK.PROVIDER_CLOSED);
}
const { method, params } = requestArguments;
const jsonRpcRequest = getJsonRpcRequest(this.#nextRequestId++, method, params);
let jsonRpcResponse;
if (this.#jsonRpcRequestWrapper !== undefined) {
jsonRpcResponse = await this.#jsonRpcRequestWrapper(jsonRpcRequest, (request) => this.#fetchJsonRpcResponse(request));
}
else {
jsonRpcResponse = await this.#fetchJsonRpcResponse(jsonRpcRequest);
}
if (isFailedJsonRpcResponse(jsonRpcResponse)) {
const error = new ProviderError(jsonRpcResponse.error.message, jsonRpcResponse.error.code);
error.data = jsonRpcResponse.error.data;
// eslint-disable-next-line no-restricted-syntax -- allow throwing ProviderError
throw error;
}
if (jsonRpcRequest.method === "evm_revert") {
this.emit(EDR_NETWORK_REVERT_SNAPSHOT_EVENT);
}
return jsonRpcResponse.result;
}
async close() {
if (this.#dispatcher !== undefined) {
// See https://github.com/nodejs/undici/discussions/3522#discussioncomment-10498734
await this.#dispatcher.close();
this.#dispatcher = undefined;
}
}
async #fetchJsonRpcResponse(jsonRpcRequest, retryCount = 0) {
const requestOptions = {
extraHeaders: {
"User-Agent": `Hardhat ${await getHardhatVersion()}`,
...this.#extraHeaders,
},
};
let response;
try {
response = await postJsonRequest(this.#url, jsonRpcRequest, requestOptions, this.#dispatcher);
}
catch (e) {
ensureError(e);
if (e instanceof ConnectionRefusedError) {
throw new HardhatError(HardhatError.ERRORS.CORE.NETWORK.CONNECTION_REFUSED, { network: this.#networkName }, e);
}
if (e instanceof RequestTimeoutError) {
throw new HardhatError(HardhatError.ERRORS.CORE.NETWORK.NETWORK_TIMEOUT, e);
}
/**
* Nodes can have a rate limit mechanism to avoid abuse. This logic checks
* if the response indicates a rate limit has been reached and retries the
* request after the specified time.
*/
if (e instanceof ResponseStatusCodeError &&
e.statusCode === TOO_MANY_REQUEST_STATUS) {
const retryAfterHeader = isObject(e.headers) && typeof e.headers["retry-after"] === "string"
? e.headers["retry-after"]
: undefined;
const retryAfterSeconds = this.#getRetryAfterSeconds(retryAfterHeader, retryCount);
if (this.#shouldRetryRequest(retryAfterSeconds, retryCount)) {
return this.#retry(jsonRpcRequest, retryAfterSeconds, retryCount);
}
// eslint-disable-next-line no-restricted-syntax -- allow throwing ProviderError
throw new LimitExceededError(undefined, e);
}
// eslint-disable-next-line no-restricted-syntax -- allow throwing ProviderError
throw new UnknownError(e.message, e);
}
return parseJsonRpcResponse(await response.body.text());
}
#getRetryAfterSeconds(retryAfterHeader, retryCount) {
const parsedRetryAfter = parseInt(`${retryAfterHeader}`, 10);
if (isNaN(parsedRetryAfter)) {
// use an exponential backoff if the retry-after header can't be parsed
return Math.min(2 ** retryCount, MAX_RETRY_WAIT_TIME_SECONDS);
}
return parsedRetryAfter;
}
#shouldRetryRequest(retryAfterSeconds, retryCount) {
if (retryCount > MAX_RETRIES) {
return false;
}
if (retryAfterSeconds > MAX_RETRY_WAIT_TIME_SECONDS) {
return false;
}
return true;
}
async #retry(request, retryAfterSeconds, retryCount) {
await sleep(retryAfterSeconds);
return this.#fetchJsonRpcResponse(request, retryCount + 1);
}
}
/**
* Gets either a pool or proxy dispatcher depending on the URL and the
* proxy configuration. This function is used internally by
* `HttpProvider.create` and should not be used directly.
*/
export async function getHttpDispatcher(url, timeout) {
let dispatcher;
const proxyUrl = shouldUseProxy(url) ? getProxyUrl(url) : undefined;
if (proxyUrl !== undefined) {
dispatcher = await getDispatcher(url, {
proxy: proxyUrl,
timeout,
});
}
else {
dispatcher = await getDispatcher(url, { pool: true, timeout });
}
return dispatcher;
}
//# sourceMappingURL=http-provider.js.map