UNPKG

hardhat

Version:

Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.

294 lines 13.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.JsonRpcClient = void 0; const ethereumjs_util_1 = require("@nomicfoundation/ethereumjs-util"); const fs_extra_1 = __importDefault(require("fs-extra")); const t = __importStar(require("io-ts")); const path_1 = __importDefault(require("path")); const base_types_1 = require("../../core/jsonrpc/types/base-types"); const block_1 = require("../../core/jsonrpc/types/output/block"); const decodeJsonRpcResponse_1 = require("../../core/jsonrpc/types/output/decodeJsonRpcResponse"); const log_1 = require("../../core/jsonrpc/types/output/log"); const receipt_1 = require("../../core/jsonrpc/types/output/receipt"); const transaction_1 = require("../../core/jsonrpc/types/output/transaction"); const hash_1 = require("../../util/hash"); const io_ts_1 = require("../../util/io-ts"); class JsonRpcClient { constructor(_httpProvider, _networkId, _latestBlockNumberOnCreation, _maxReorg, _forkCachePath) { this._httpProvider = _httpProvider; this._networkId = _networkId; this._latestBlockNumberOnCreation = _latestBlockNumberOnCreation; this._maxReorg = _maxReorg; this._forkCachePath = _forkCachePath; this._cache = new Map(); } getNetworkId() { return this._networkId; } async getDebugTraceTransaction(transactionHash) { return this._perform("debug_traceTransaction", [(0, ethereumjs_util_1.bytesToHex)(transactionHash)], t.object, () => undefined); } // Storage key must be 32 bytes long async getStorageAt(address, position, blockNumber) { return this._perform("eth_getStorageAt", [ address.toString(), (0, base_types_1.numberToRpcQuantity)(position), (0, base_types_1.numberToRpcQuantity)(blockNumber), ], base_types_1.rpcData, () => blockNumber); } async getBlockByNumber(blockNumber, includeTransactions = false) { if (includeTransactions) { return this._perform("eth_getBlockByNumber", [(0, base_types_1.numberToRpcQuantity)(blockNumber), true], (0, io_ts_1.nullable)(block_1.rpcBlockWithTransactions), (block) => block?.number ?? undefined); } return this._perform("eth_getBlockByNumber", [(0, base_types_1.numberToRpcQuantity)(blockNumber), false], (0, io_ts_1.nullable)(block_1.rpcBlock), (block) => block?.number ?? undefined); } async getBlockByHash(blockHash, includeTransactions = false) { if (includeTransactions) { return this._perform("eth_getBlockByHash", [(0, ethereumjs_util_1.bytesToHex)(blockHash), true], (0, io_ts_1.nullable)(block_1.rpcBlockWithTransactions), (block) => block?.number ?? undefined); } return this._perform("eth_getBlockByHash", [(0, ethereumjs_util_1.bytesToHex)(blockHash), false], (0, io_ts_1.nullable)(block_1.rpcBlock), (block) => block?.number ?? undefined); } async getTransactionByHash(transactionHash) { return this._perform("eth_getTransactionByHash", [(0, ethereumjs_util_1.bytesToHex)(transactionHash)], (0, io_ts_1.nullable)(transaction_1.rpcTransaction), (tx) => tx?.blockNumber ?? undefined); } async getTransactionCount(address, blockNumber) { return this._perform("eth_getTransactionCount", [(0, ethereumjs_util_1.bytesToHex)(address), (0, base_types_1.numberToRpcQuantity)(blockNumber)], base_types_1.rpcQuantity, () => blockNumber); } async getTransactionReceipt(transactionHash) { return this._perform("eth_getTransactionReceipt", [(0, ethereumjs_util_1.bytesToHex)(transactionHash)], (0, io_ts_1.nullable)(receipt_1.rpcTransactionReceipt), (tx) => tx?.blockNumber ?? undefined); } async getLogs(options) { let address; if (options.address !== undefined) { address = Array.isArray(options.address) ? options.address.map((x) => (0, ethereumjs_util_1.bytesToHex)(x)) : (0, ethereumjs_util_1.bytesToHex)(options.address); } let topics; if (options.topics !== undefined) { topics = options.topics.map((items) => items !== null ? items.map((x) => (x !== null ? (0, ethereumjs_util_1.bytesToHex)(x) : x)) : null); } return this._perform("eth_getLogs", [ { fromBlock: (0, base_types_1.numberToRpcQuantity)(options.fromBlock), toBlock: (0, base_types_1.numberToRpcQuantity)(options.toBlock), address, topics, }, ], t.array(log_1.rpcLog, "RpcLog Array"), () => options.toBlock); } async getAccountData(address, blockNumber) { const results = await this._performBatch([ { method: "eth_getCode", params: [address.toString(), (0, base_types_1.numberToRpcQuantity)(blockNumber)], tType: base_types_1.rpcData, }, { method: "eth_getTransactionCount", params: [address.toString(), (0, base_types_1.numberToRpcQuantity)(blockNumber)], tType: base_types_1.rpcQuantity, }, { method: "eth_getBalance", params: [address.toString(), (0, base_types_1.numberToRpcQuantity)(blockNumber)], tType: base_types_1.rpcQuantity, }, ], () => blockNumber); return { code: results[0], transactionCount: results[1], balance: results[2], }; } async getLatestBlockNumber() { return this._perform("eth_blockNumber", [], base_types_1.rpcQuantity, (blockNumber) => blockNumber); } async _perform(method, params, tType, getMaxAffectedBlockNumber) { const cacheKey = this._getCacheKey(method, params); const cachedResult = this._getFromCache(cacheKey); if (cachedResult !== undefined) { return cachedResult; } if (this._forkCachePath !== undefined) { const diskCachedResult = await this._getFromDiskCache(this._forkCachePath, cacheKey, tType); if (diskCachedResult !== undefined) { this._storeInCache(cacheKey, diskCachedResult); return diskCachedResult; } } const rawResult = await this._send(method, params); const decodedResult = (0, decodeJsonRpcResponse_1.decodeJsonRpcResponse)(rawResult, tType); const blockNumber = getMaxAffectedBlockNumber(decodedResult); if (this._canBeCached(blockNumber)) { this._storeInCache(cacheKey, decodedResult); if (this._forkCachePath !== undefined) { await this._storeInDiskCache(this._forkCachePath, cacheKey, rawResult); } } return decodedResult; } async _performBatch(batch, getMaxAffectedBlockNumber) { // Perform Batch caches the entire batch at once. // It could implement something more clever, like caching per request // but it's only used in one place, and those other requests aren't // used anywhere else. const cacheKey = this._getBatchCacheKey(batch); const cachedResult = this._getFromCache(cacheKey); if (cachedResult !== undefined) { return cachedResult; } if (this._forkCachePath !== undefined) { const diskCachedResult = await this._getBatchFromDiskCache(this._forkCachePath, cacheKey, batch.map((b) => b.tType)); if (diskCachedResult !== undefined) { this._storeInCache(cacheKey, diskCachedResult); return diskCachedResult; } } const rawResults = await this._sendBatch(batch); const decodedResults = rawResults.map((result, i) => (0, decodeJsonRpcResponse_1.decodeJsonRpcResponse)(result, batch[i].tType)); const blockNumber = getMaxAffectedBlockNumber(decodedResults); if (this._canBeCached(blockNumber)) { this._storeInCache(cacheKey, decodedResults); if (this._forkCachePath !== undefined) { await this._storeInDiskCache(this._forkCachePath, cacheKey, rawResults); } } return decodedResults; } async _send(method, params, isRetryCall = false) { try { return await this._httpProvider.request({ method, params }); } catch (err) { if (this._shouldRetry(isRetryCall, err)) { return this._send(method, params, true); } // This is a workaround for this TurboGeth bug: https://github.com/ledgerwatch/turbo-geth/issues/1645 const errMessage = err.message; if (err.code === -32000 && errMessage.includes("not found")) { return null; } // eslint-disable-next-line @nomicfoundation/hardhat-internal-rules/only-hardhat-error throw err; } } async _sendBatch(batch, isRetryCall = false) { try { return await this._httpProvider.sendBatch(batch); } catch (err) { if (this._shouldRetry(isRetryCall, err)) { return this._sendBatch(batch, true); } // eslint-disable-next-line @nomicfoundation/hardhat-internal-rules/only-hardhat-error throw err; } } _shouldRetry(isRetryCall, err) { const errMessage = err.message; const isRetriableError = errMessage.includes("header not found") || errMessage.includes("connect ETIMEDOUT"); const isServiceUrl = this._httpProvider.url.includes("infura") || this._httpProvider.url.includes("alchemyapi"); return (!isRetryCall && isServiceUrl && err instanceof Error && isRetriableError); } _getCacheKey(method, params) { const networkId = this.getNetworkId(); const plaintextKey = `${networkId} ${method} ${JSON.stringify(params)}`; const hashed = (0, hash_1.createNonCryptographicHashBasedIdentifier)(Buffer.from(plaintextKey, "utf8")); return hashed.toString("hex"); } _getBatchCacheKey(batch) { let fakeMethod = ""; const fakeParams = []; for (const entry of batch) { fakeMethod += entry.method; fakeParams.push(...entry.params); } return this._getCacheKey(fakeMethod, fakeParams); } _getFromCache(cacheKey) { return this._cache.get(cacheKey); } _storeInCache(cacheKey, decodedResult) { this._cache.set(cacheKey, decodedResult); } async _getFromDiskCache(forkCachePath, cacheKey, tType) { const rawResult = await this._getRawFromDiskCache(forkCachePath, cacheKey); if (rawResult !== undefined) { return (0, decodeJsonRpcResponse_1.decodeJsonRpcResponse)(rawResult, tType); } } async _getBatchFromDiskCache(forkCachePath, cacheKey, tTypes) { const rawResults = await this._getRawFromDiskCache(forkCachePath, cacheKey); if (!Array.isArray(rawResults)) { return undefined; } return rawResults.map((r, i) => (0, decodeJsonRpcResponse_1.decodeJsonRpcResponse)(r, tTypes[i])); } async _getRawFromDiskCache(forkCachePath, cacheKey) { try { return await fs_extra_1.default.readJSON(this._getDiskCachePathForKey(forkCachePath, cacheKey), { encoding: "utf8", }); } catch (error) { if (error.code === "ENOENT") { return undefined; } // eslint-disable-next-line @nomicfoundation/hardhat-internal-rules/only-hardhat-error throw error; } } async _storeInDiskCache(forkCachePath, cacheKey, rawResult) { const requestPath = this._getDiskCachePathForKey(forkCachePath, cacheKey); await fs_extra_1.default.ensureDir(path_1.default.dirname(requestPath)); await fs_extra_1.default.writeJSON(requestPath, rawResult, { encoding: "utf8", }); } _getDiskCachePathForKey(forkCachePath, key) { return path_1.default.join(forkCachePath, `network-${this._networkId}`, `request-${key}.json`); } _canBeCached(blockNumber) { if (blockNumber === undefined) { return false; } return !this._canBeReorgedOut(blockNumber); } _canBeReorgedOut(blockNumber) { const maxSafeBlockNumber = this._latestBlockNumberOnCreation - this._maxReorg; return blockNumber > maxSafeBlockNumber; } } exports.JsonRpcClient = JsonRpcClient; //# sourceMappingURL=client.js.map