UNPKG

blockchain-api

Version:

API utilities for interacting with the Exatechl2 blockchain

418 lines (417 loc) 18.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getLatestBlockNumber = getLatestBlockNumber; exports.getBlockByNumber = getBlockByNumber; exports.getBlockByHash = getBlockByHash; exports.getBlockTransactionCount = getBlockTransactionCount; exports.getLatestBlocks = getLatestBlocks; exports.getBlockchainStats = getBlockchainStats; exports.getEnhancedBlockDetails = getEnhancedBlockDetails; const rpc_1 = require("../../core/rpc"); const explorerApi_1 = require("../../core/explorerApi"); const utils_1 = require("./utils"); const formatters_1 = require("../../utils/formatters"); const logger_1 = require("../../utils/logger"); /** * * @returns {Promise<string>} */ async function getLatestBlockNumber() { return (0, rpc_1.callRpc)('eth_blockNumber'); } /** * * @param {string|number} blockNumber * @param {boolean} includeTransactions * @returns {Promise<Block>} */ async function getBlockByNumber(blockNumber, includeTransactions = false) { const blockNumberHex = typeof blockNumber === 'number' ? '0x' + blockNumber.toString(16) : blockNumber; const block = await (0, rpc_1.callRpc)('eth_getBlockByNumber', [blockNumberHex, includeTransactions]); return block; } /** * * @param {string} blockHash * @param {boolean} includeTransactions * @returns {Promise<Block>} */ async function getBlockByHash(blockHash, includeTransactions = false) { const block = await (0, rpc_1.callRpc)('eth_getBlockByHash', [blockHash, includeTransactions]); return block; } /** * * @param {string} blockNumber * @returns {Promise<number>} */ async function getBlockTransactionCount(blockNumber) { const result = await (0, rpc_1.callRpc)('eth_getBlockTransactionCountByNumber', [blockNumber]); return (0, formatters_1.parseHexToDecimal)(result); } /** * * @param {number} page * @param {number} perPage * @param {boolean} includeTransactions * @param {boolean} debug * @returns {Promise<PaginatedBlocks>} */ async function getLatestBlocks(page = 1, perPage = 10, includeTransactions = false, debug = false) { try { if (debug) logger_1.logger.debug(`Fetching blocks: page=${page}, perPage=${perPage}, includeTransactions=${includeTransactions}`); const response = await (0, explorerApi_1.callExplorerApi)('blocks', { limit: perPage, offset: (page - 1) * perPage, include_transactions: includeTransactions ? 1 : 0 }); if (debug) { logger_1.logger.debug('Full API Response Structure:', JSON.stringify(response, null, 2).substring(0, 300) + '...'); logger_1.logger.debug('Response received, processing blocks...'); } let blockData = []; if (Array.isArray(response)) { blockData = response; } else if (response.data && Array.isArray(response.data)) { blockData = response.data; } else if (response.blocks && Array.isArray(response.blocks)) { blockData = response.blocks; } else { logger_1.logger.error('Unexpected API response format:', response); throw new Error('Unexpected API response format. Check logs for details.'); } if (debug) logger_1.logger.debug(`Processing ${blockData.length} blocks...`); const blocks = blockData.map((blockData, index) => { var _a, _b, _c, _d; const block = { number: blockData.number ? (((_b = (_a = blockData.number).startsWith) === null || _b === void 0 ? void 0 : _b.call(_a, '0x')) ? blockData.number : `0x${parseInt(blockData.number).toString(16)}`) : '0x0', hash: blockData.hash || '', timestamp: (0, formatters_1.standardizeTimestamp)(blockData.timestamp, true) || Date.now(), parentHash: blockData.parent_hash || blockData.parentHash || '', miner: blockData.miner || blockData.validator || '', transactions: blockData.transactions || [], txCount: blockData.transactions_count || blockData.transactionsCount || (blockData.transactions ? blockData.transactions.length : 0), size: blockData.size ? (((_d = (_c = blockData.size).startsWith) === null || _d === void 0 ? void 0 : _d.call(_c, '0x')) ? blockData.size : `0x${parseInt(blockData.size).toString(16)}`) : undefined, gasUsed: blockData.gas_used || blockData.gasUsed ? (String(blockData.gas_used || blockData.gasUsed).startsWith('0x') ? String(blockData.gas_used || blockData.gasUsed) : `0x${parseInt(String(blockData.gas_used || blockData.gasUsed)).toString(16)}`) : undefined, gasLimit: blockData.gas_limit || blockData.gasLimit ? (String(blockData.gas_limit || blockData.gasLimit).startsWith('0x') ? String(blockData.gas_limit || blockData.gasLimit) : `0x${parseInt(String(blockData.gas_limit || blockData.gasLimit)).toString(16)}`) : undefined, baseFeePerGas: blockData.base_fee_per_gas || blockData.baseFeePerGas ? (String(blockData.base_fee_per_gas || blockData.baseFeePerGas).startsWith('0x') ? String(blockData.base_fee_per_gas || blockData.baseFeePerGas) : `0x${parseInt(String(blockData.base_fee_per_gas || blockData.baseFeePerGas)).toString(16)}`) : undefined, difficulty: blockData.difficulty || '', totalDifficulty: blockData.total_difficulty || blockData.totalDifficulty || '', extraData: blockData.extra_data || blockData.extraData || '', stateRoot: blockData.state_root || blockData.stateRoot || '', receiptsRoot: blockData.receipts_root || blockData.receiptsRoot || '', logsBloom: blockData.logs_bloom || blockData.logsBloom || '', nonce: blockData.nonce || '', mixHash: blockData.mix_hash || blockData.mixHash || '', sha3Uncles: blockData.sha3_uncles || blockData.sha3Uncles || '', status: blockData.status || blockData.batch_status || '', batchId: blockData.batch_id || blockData.batchId, batchStatus: blockData.batch_status || blockData.batchStatus }; return (0, utils_1.formatBlockForDisplay)(block); }); const totalCount = (response.meta && response.meta.total) || response.total || response.totalCount || response.count || (response.meta && response.meta.count) || blocks.length; const totalPages = (response.meta && response.meta.total_pages) || Math.ceil(totalCount / perPage); if (debug) logger_1.logger.debug(`Returning ${blocks.length} blocks, totalCount: ${totalCount}, totalPages: ${totalPages}`); return { blocks, totalCount, totalPages, page, perPage }; } catch (error) { logger_1.logger.error('Error fetching blocks from Explorer DB:', error); throw new Error(`Failed to get blocks from Explorer DB: ${error.message}`); } } /** * * @param {number} sampleSize * @returns {Promise<BlockchainStats>} */ async function getBlockchainStats(sampleSize = 10) { try { const latestBlockHex = await getLatestBlockNumber(); const latestBlockNumber = (0, formatters_1.parseHexToDecimal)(latestBlockHex); const recentBlocks = []; for (let i = 0; i < sampleSize; i++) { const blockNumber = latestBlockNumber - i; if (blockNumber < 0) break; const block = await getBlockByNumber(blockNumber, false); if (block) recentBlocks.push(block); } let avgBlockTime = 0; let blockTimeChange = 0; if (recentBlocks.length >= 2) { const blockTimes = []; for (let i = 1; i < recentBlocks.length; i++) { try { const prevDate = (0, formatters_1.parseBlockchainTimestamp)(recentBlocks[i - 1].timestamp); const currDate = (0, formatters_1.parseBlockchainTimestamp)(recentBlocks[i].timestamp); const prevTimestamp = prevDate.getTime() / 1000; const currTimestamp = currDate.getTime() / 1000; blockTimes.push(prevTimestamp - currTimestamp); } catch (error) { logger_1.logger.error('Error parsing block timestamp:', error); continue; } } avgBlockTime = blockTimes.reduce((sum, time) => sum + time, 0) / blockTimes.length; if (blockTimes.length >= 6) { const halfIndex = Math.floor(blockTimes.length / 2); const firstHalf = blockTimes.slice(0, halfIndex).reduce((sum, time) => sum + time, 0) / halfIndex; const secondHalf = blockTimes.slice(halfIndex).reduce((sum, time) => sum + time, 0) / (blockTimes.length - halfIndex); blockTimeChange = ((firstHalf - secondHalf) / secondHalf) * 100; } } const txCounts = await Promise.all(recentBlocks.slice(0, 5).map(block => { if (Array.isArray(block.transactions)) { return block.transactions.length; } else { return getBlockTransactionCount(block.number); } })); const avgTxPerBlock = txCounts.reduce((sum, count) => sum + count, 0) / txCounts.length; const totalTransactions = Math.round(latestBlockNumber * avgTxPerBlock); let txPerBlockChange = 0; if (txCounts.length >= 6) { const halfIndex = Math.floor(txCounts.length / 2); const firstHalf = txCounts.slice(0, halfIndex).reduce((sum, count) => sum + count, 0) / halfIndex; const secondHalf = txCounts.slice(halfIndex).reduce((sum, count) => sum + count, 0) / (txCounts.length - halfIndex); txPerBlockChange = ((firstHalf - secondHalf) / secondHalf) * 100; } return { totalBlocks: latestBlockNumber + 1, avgBlockTime, blockTimeChange, avgTxPerBlock, txPerBlockChange, totalTransactions }; } catch (error) { logger_1.logger.error('Error calculating blockchain stats:', error); throw error; } } async function enhanceTransactionsWithReceipts(transactions) { try { if (!transactions || transactions.length === 0 || typeof transactions[0] === 'string') { return transactions; } const { getTransactionReceipt } = require('../transactions'); const enhancedTransactions = await Promise.all(transactions.map(async (tx) => { try { const receipt = await getTransactionReceipt(tx.hash); return { ...tx, gasUsed: receipt.gasUsed, effectiveGasPrice: receipt.effectiveGasPrice, status: receipt.status, cumulativeGasUsed: receipt.cumulativeGasUsed, contractAddress: receipt.contractAddress }; } catch (error) { logger_1.logger.error(`Error getting receipt for transaction ${tx.hash}:`, error); return tx; } })); return enhancedTransactions; } catch (error) { logger_1.logger.error('Error enhancing transactions with receipts:', error); return transactions; } } /** * * @param {string} blockId * @param {boolean} includeTransactions * @returns {Promise<EnhancedBlock>} */ async function getEnhancedBlockDetails(blockId, includeTransactions = true) { let blockData; let blockNumber; if (blockId.startsWith('B-')) { blockNumber = parseInt(blockId.replace('B-', '')); if (isNaN(blockNumber)) { throw new Error('Invalid block number format'); } blockData = await getBlockByNumber(blockNumber, includeTransactions); } else if (blockId.startsWith('0x')) { blockData = await getBlockByHash(blockId, includeTransactions); blockNumber = (0, formatters_1.parseHexToDecimal)(blockData.number); } else { blockNumber = parseInt(blockId); if (isNaN(blockNumber)) { throw new Error('Invalid block identifier'); } blockData = await getBlockByNumber(blockNumber, includeTransactions); } if (!blockData) { throw new Error('Block data not found'); } let batchId = undefined; let batchStatus = undefined; try { logger_1.logger.debug(`Fetching batch information for block ${blockNumber} from Explorer DB`); const explorerBlockData = await (0, explorerApi_1.callExplorerApi)(`blocks/${blockNumber}`); if (explorerBlockData && explorerBlockData.data) { batchId = explorerBlockData.data.batch_id; batchStatus = explorerBlockData.data.batch_status; logger_1.logger.debug(`Found batch info for block ${blockNumber}: ID=${batchId}, Status=${batchStatus}`); blockData.batchId = batchId; blockData.batchStatus = batchStatus; } } catch (error) { logger_1.logger.warn(`Could not fetch batch information from Explorer DB for block ${blockNumber}:`, error); } if (Array.isArray(blockData.transactions) && blockData.transactions.length > 0 && typeof blockData.transactions[0] === 'object') { try { const blockDate = (0, formatters_1.parseBlockchainTimestamp)(blockData.timestamp); const blockTimestamp = blockDate.getTime(); blockData.transactions = blockData.transactions.map((tx) => { if (!tx.timestamp) { return { ...tx, timestamp: blockTimestamp }; } return tx; }); if (includeTransactions) { blockData.transactions = await enhanceTransactionsWithReceipts(blockData.transactions); } } catch (error) { logger_1.logger.error('Error processing block timestamp for transactions:', error); } } const formattedBlock = (0, utils_1.formatBlockForDisplay)(blockData); let formattedTransactions = []; if (Array.isArray(blockData.transactions)) { if (blockData.transactions.length > 0 && typeof blockData.transactions[0] === 'object') { try { const { formatTransactionsForDisplay } = require('../transactions'); if (typeof formatTransactionsForDisplay === 'function') { formattedTransactions = formatTransactionsForDisplay(blockData.transactions); } else { formattedTransactions = blockData.transactions.map((tx) => ({ hash: tx.hash, from: tx.from, to: tx.to, value: tx.value || '0', status: tx.status || 'unknown', gasUsed: tx.gasUsed, gasPrice: tx.gasPrice, gas: tx.gas, nonce: tx.nonce, input: tx.input, timestamp: formattedBlock.timestamp })); } } catch (error) { logger_1.logger.error('Error formatting transactions:', error); formattedTransactions = blockData.transactions.map((tx) => ({ hash: tx.hash, from: tx.from || 'Unknown', to: tx.to || 'Unknown', value: tx.value || '0', status: tx.status || 'unknown', gasUsed: tx.gasUsed, gasPrice: tx.gasPrice, gas: tx.gas, nonce: tx.nonce, input: tx.input, timestamp: formattedBlock.timestamp })); } } else { formattedTransactions = blockData.transactions.map((hash) => ({ hash, from: 'Unknown', to: 'Unknown', value: '0', status: 'unknown', timestamp: formattedBlock.timestamp })); } } const rootHash = blockData.stateRoot || blockData.receiptsRoot || undefined; let l1BlockNumber = undefined; if (blockData.l1BlockNumber) { if (typeof blockData.l1BlockNumber === 'string' && blockData.l1BlockNumber.startsWith('0x')) { l1BlockNumber = parseInt(blockData.l1BlockNumber, 16); } else { l1BlockNumber = Number(blockData.l1BlockNumber); } } const enhancedBlock = { ...formattedBlock, transactions: formattedTransactions, txCount: formattedTransactions.length, transferTxCount: formattedTransactions.filter((tx) => tx.method === 'Transfer').length, contractTxCount: formattedTransactions.filter((tx) => tx.method === 'Contract Call' || tx.method === 'Contract Creation').length, miner: blockData.miner, validator: blockData.validator || formattedBlock.validator, parentHash: blockData.parentHash, rootHash: rootHash, stateRoot: blockData.stateRoot, receiptsRoot: blockData.receiptsRoot, extraData: blockData.extraData, validatorBlocks: Math.floor(Math.random() * 100) + 50, difficulty: blockData.difficulty, totalDifficulty: blockData.totalDifficulty, nonce: blockData.nonce, mixHash: blockData.mixHash, sha3Uncles: blockData.sha3Uncles, logsBloom: blockData.logsBloom, sendCount: blockData.sendCount, sendRoot: blockData.sendRoot, l1BlockNumber, batchId: blockData.batchId, batchStatus: blockData.batchStatus }; return enhancedBlock; }