UNPKG

noob-ethereum

Version:
354 lines (353 loc) 15.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.Provider = void 0; const range_1 = __importDefault(require("lodash/range")); const http_1 = require("../../abstract/http"); const dotenv = __importStar(require("dotenv")); dotenv.config(); const export_1 = require("../utils/export"); const constants_1 = require("../../constants"); const index_1 = require("../../index"); const config = { headers: { 'Content-Type': 'application/json', }, }; class Provider extends http_1.HttpClient { constructor(url) { super(url); this.config = config; } static init(url) { if (!this.instance) { this.instance = new Provider(url); } return this.instance; } /** * Fetch raw block by number via JSON-RPC * @param {string} blockNumber - 12396599 * @param {boolean} verbose - true * @returns {Promise<IRawBlock>} */ async getBlockByNumber(blockNumber, verbose = false) { var _a; // @ts-ignore if (isNaN(blockNumber) || blockNumber === '') { throw new Error('User supplied invalid string as block number'); } if (+blockNumber < 0) { throw new Error('User supplied block number that does not exist'); } const res = await this.instance.post('', { jsonrpc: '2.0', method: 'eth_getBlockByNumber', params: [index_1.utils.hexify(blockNumber), verbose], id: 0, }, this.config); if ((_a = res.data.error) === null || _a === void 0 ? void 0 : _a.code) { throw new Error(res.data.error.code); } const { result } = res.data; return result; } async getLogs(address, topics, blockHash) { var _a; const res = await this.instance.post('', { jsonrpc: '2.0', method: 'eth_getLogs', params: [ { address, topics, blockHash, }, ], id: 0, }, this.config); if ((_a = res.data.error) === null || _a === void 0 ? void 0 : _a.code) { throw new Error(res.data.error.code); } const { result } = res.data; return result; } /** * Fetch latest raw block via JSON-RPC * @param {boolean} verbose - true * @returns {Promise<IRawBlock>} */ async getLatestBlock(verbose = false) { const res = await this.instance.post('', { jsonrpc: '2.0', method: 'eth_getBlockByNumber', params: ['latest', verbose], id: 0, }, this.config); const { result } = res.data; return result; } /** * Fetch transaction receipt by transaction hash * @param {string} hash - transaction hash * @returns {Promise<IRawBlock>} */ async getTransactionReceipt(hash) { var _a; const res = await this.instance.post('', { jsonrpc: '2.0', method: 'eth_getTransactionReceipt', params: [hash], id: 1, }, this.config); if ((_a = res.data.error) === null || _a === void 0 ? void 0 : _a.code) { throw new Error(res.data.error.code); } const { result } = res.data; return result; } /** * Generate JSON file of latest block * @param {boolean} verbose - flag to specify fetching full tx objects or just their hashes * @param {string} path - optional parameter to specify path from project root where to save JSON file */ async seedLatestBlock(verbose = false, path = 'src/seeder/blocks/1559') { const block = await this.getLatestBlock(verbose); const blockNumber = parseInt(block.number, 16); (0, export_1.exportToJSONFile)(block, blockNumber.toString(), path); } /** * Generate JSON file for a specific block * @param {number} num - decimal number of a block * @param {boolean} verbose - flag to specify fetching full tx objects or just their hashes * @param {string} path - optional parameter to specify path from project root where to save JSON file */ async seedBlockByNumber(verbose = false, num, path) { if (!num) throw new Error('No block number specified'); const block = await this.getBlockByNumber(num, verbose); if (!path) { path = num >= constants_1.LONDON_HARDFORK_BLOCK ? 'src/seeder/blocks/1559' : 'src/seeder/blocks/legacy'; } (0, export_1.exportToJSONFile)(block, num.toString(), path); } async _prepareBlockRangeQuery(starting, total) { const currentHead = index_1.utils.decimal((await this.getLatestBlock(false)).number); const startBlock = index_1.utils.decimal((await this.getBlockByNumber(starting, true)).number); if (total === 'latest') { return (0, range_1.default)(startBlock, currentHead, 1); } if (startBlock + total > currentHead) { throw new Error('Range provided includes blocks that have not been added to the chain yet!'); } return (0, range_1.default)(startBlock, startBlock + total, 1); } async _fetchFullTransactionBodies(startingBlock, total) { const blockNumberArr = await this._prepareBlockRangeQuery(startingBlock, total); const txHashArr = []; const fetchBlockClosure = async (n, i) => { const { transactions } = await this.getBlockByNumber(n, true); txHashArr[i] = []; transactions.map((t) => { txHashArr[i].push(t.toString()); }); }; await Promise.all(blockNumberArr.map((n, i) => fetchBlockClosure(n, i))); return txHashArr; } /* Fetch array block transactions in tuple form over a range of blocks (tuple includes stringified array of block number, transaction index, transaction hash) */ async _fetchTransactionsOverBlockRange(startingBlock, total) { const blockNumberArr = await this._prepareBlockRangeQuery(startingBlock, total); const txHashArr = []; const fetchBlockClosure = async (n, i) => { const { transactions } = await this.getBlockByNumber(n, true); txHashArr[i] = []; transactions.map((t) => { txHashArr[i].push([n, index_1.utils.decimal(t.transactionIndex), t.hash].toString()); }); }; await Promise.all(blockNumberArr.map((n, i) => fetchBlockClosure(n, i))); return txHashArr; } /* Go over a specified number of blocks are return transaction tuples that match the from and to transacction fields */ async _fetchTransactionsOverBlocksByInteraction(startingBlock, total, from, to) { console.log('block:', startingBlock, 'total:', total); const blockNumberArr = await this._prepareBlockRangeQuery(startingBlock, total); let txHashArr = []; const fetchBlockClosure = async (n, i) => { const { transactions } = await this.getBlockByNumber(n, true); txHashArr[i] = []; transactions.map((t) => { if (t.from === from && t.to === to) { txHashArr[i].push([n, index_1.utils.decimal(t.transactionIndex), t.hash].toString()); } }); }; await Promise.all(blockNumberArr.map((n, i) => fetchBlockClosure(n, i))); // Filter all the blocks that did not have those interactions txHashArr = txHashArr.filter((arr) => arr.length !== 0); return txHashArr; } // Process and handle millions of requests for ALL block transactions async fetchTransactionsOverBlockRange(startingBlock, total, limit) { const CONCURRENT_LIMIT = limit; const start = Date.now(); let result = []; let params = await this._prepareBlockRangeQuery(startingBlock, total); const blockTotal = params.length; let finalGroup = []; let progress = 0; const concGroupSize = Math.floor(params.length / CONCURRENT_LIMIT); if (params.length % CONCURRENT_LIMIT !== 0) { const sliceParam = -params.length % CONCURRENT_LIMIT; finalGroup = [...params.slice(sliceParam)]; params = [...params.slice(0, sliceParam)]; console.log('params.length:', params.length); console.log('finalGroup.length:', finalGroup.length); } for (let i = 0; i < params.length; i += CONCURRENT_LIMIT) { const arr = await this._fetchTransactionsOverBlockRange(startingBlock + i, CONCURRENT_LIMIT); result = result.concat(arr); progress += CONCURRENT_LIMIT; console.log('Blocks downloaded:', `${progress}/${blockTotal}`, '| Progress:', ((100 * progress) / blockTotal).toFixed(1) + '%' + ' | ' + 'elapsed time: ', index_1.utils.minutes(Date.now() - start)); } if (finalGroup.length > 0) { const arr = await this._fetchTransactionsOverBlockRange(finalGroup[0], finalGroup.length); result = result.concat(arr); progress += finalGroup.length; console.log('Blocks downloaded:', `${progress}/${blockTotal}`, '| Progress:', ((100 * progress) / blockTotal).toFixed(1) + '%' + ' | ' + 'elapsed time: ', index_1.utils.minutes(Date.now() - start)); } console.log('group length:', concGroupSize); console.log('final group length:', finalGroup.length); return result; } async fetchTransactionsOverBlocksByInteraction(startingBlock, total, limit, from, to) { const CONCURRENT_LIMIT = limit; const start = Date.now(); let result = []; let params = await this._prepareBlockRangeQuery(startingBlock, total); const blockTotal = params.length; let finalGroup = []; let progress = 0; const concGroupSize = Math.floor(params.length / CONCURRENT_LIMIT); if (params.length % CONCURRENT_LIMIT !== 0) { const sliceParam = -params.length % CONCURRENT_LIMIT; finalGroup = [...params.slice(sliceParam)]; params = [...params.slice(0, sliceParam)]; console.log('params.length:', params.length); console.log('finalGroup.length:', finalGroup.length); } for (let i = 0; i < params.length; i += CONCURRENT_LIMIT) { const arr = await this._fetchTransactionsOverBlocksByInteraction(startingBlock + i, CONCURRENT_LIMIT, from, to); result = result.concat(arr); progress += CONCURRENT_LIMIT; console.log('Blocks downloaded:', `${progress}/${blockTotal}`, '| Progress:', ((100 * progress) / blockTotal).toFixed(1) + '%' + ' | ' + 'elapsed time: ', index_1.utils.minutes(Date.now() - start)); } if (finalGroup.length > 0) { const arr = await this._fetchTransactionsOverBlocksByInteraction(finalGroup[0], finalGroup.length, from, to); result = result.concat(arr); progress += finalGroup.length; console.log('Blocks downloaded:', `${progress}/${blockTotal}`, '| Progress:', ((100 * progress) / blockTotal).toFixed(1) + '%' + ' | ' + 'elapsed time: ', index_1.utils.minutes(Date.now() - start)); } console.log('group length:', concGroupSize); console.log('final group length:', finalGroup.length); return result; } async fetchMultipleRequests(paramsArr) { const result = []; const requestClosure = async (hash, i) => { const res = await this.getTransactionReceipt(hash); const standardized = this._standardizeTransactionReceipt(res); result[i] = []; result[i].push(standardized); }; await Promise.all(paramsArr.map((hash, i) => requestClosure(hash, i))); return result; } async fetchBatchReceipts(batchArr, limit) { let result = []; const CONCURRENT_LIMIT = limit; const start = Date.now(); const total = batchArr.length; let arr = batchArr.flat(Infinity).map((x) => { const tuple = x.split(','); return tuple[tuple.length - 1]; }); let progress = 0; let finalRequestBatch = []; if (arr.length % CONCURRENT_LIMIT !== 0) { const param = -arr.length % CONCURRENT_LIMIT; finalRequestBatch = [...arr.slice(param)]; arr = [...arr.slice(0, param)]; console.log(param); console.log(CONCURRENT_LIMIT); console.log('params.length:', arr.length); console.log('finalGroup.length:', finalRequestBatch.length); } for (let i = 0; i < arr.length; i += CONCURRENT_LIMIT) { const res = await this.fetchMultipleRequests(arr.slice(i, i + CONCURRENT_LIMIT)); result = [ ...result, ...res.flat(2).map((obj, i) => { obj.batchNumber = progress + i + 1; return obj; }), ]; progress += CONCURRENT_LIMIT; console.log('Receipts downloaded:', `${progress}/${total}`, '| Progress:', ((100 * progress) / total).toFixed(1) + '%' + ' | ' + 'elapsed time: ', index_1.utils.minutes(Date.now() - start)); } if (finalRequestBatch.length > 0) { const res = await this.fetchMultipleRequests(finalRequestBatch); result = [ ...result, ...res.flat(2).map((obj, i) => { obj.batchNumber = progress + i + 1; return obj; }), ]; progress += finalRequestBatch.length; console.log('Receipts downloaded:', `${progress}/${total}`, '| Progress:', ((100 * progress) / total).toFixed(1) + '%' + ' | ' + 'elapsed time: ', index_1.utils.minutes(Date.now() - start)); } console.log('batches scraped:', total); console.log('group length:', CONCURRENT_LIMIT); console.log('final group length:', finalRequestBatch.length); return result; } _standardizeTransactionReceipt(receipt) { const effectiveGasPrice = index_1.utils.toGwei(receipt.effectiveGasPrice, 'wei'); const gasUsed = parseInt(receipt.gasUsed, 16); return { transactionHash: receipt.transactionHash, blockNumber: parseInt(receipt.blockNumber, 16), effectiveGasPrice, gasUsed, publicationCost: index_1.utils.gweiToEther(gasUsed * effectiveGasPrice), }; } } exports.Provider = Provider;