UNPKG

@bcoders.gr/eth-provider

Version:

High-Performance IPC Ethereum Provider with Advanced Optimizations - Refactored for Better Performance

288 lines (241 loc) 9.69 kB
import { ConnectionManager } from './connection-manager.js'; import { CacheManager } from './cache-manager.js'; import { JSONParser } from './json-parser.js'; import { BatchProcessor } from './batch-processor.js'; import { MetricsManager } from './metrics-manager.js'; import { RequestPool } from './request-pool.js'; import { v4 as uuidv4 } from 'uuid'; /** * High-Performance IPC Ethereum Provider with Advanced Optimizations * Refactored version with modular architecture and improved performance */ export class IPCProvider { constructor(ipcPath = '/home/chain/exec/geth.ipc', options = {}) { this.ipcPath = ipcPath; this.logger = options.logger || console; // Initialize core components this.connection = new ConnectionManager(ipcPath, { ...options, logger: this.logger }); this.cache = new CacheManager({ enabled: options.cacheEnabled !== false, maxSize: options.cacheSize || 1000, defaultTTL: options.cacheTTL || 5000, logger: this.logger }); this.jsonParser = new JSONParser({ bufferSize: options.bufferSize || 2 * 1024 * 1024, logger: this.logger }); this.batchProcessor = new BatchProcessor({ enabled: options.batchRequests !== false, batchSize: options.batchSize || 10, batchTimeout: options.batchTimeout || 10, logger: this.logger }); this.metrics = new MetricsManager({ enabled: options.metricsEnabled !== false, trackResponseTimes: true, trackMemoryUsage: true, logger: this.logger }); this.requestPool = new RequestPool({ poolSize: options.poolSize || 100, maxPoolSize: options.maxPoolSize || 500, logger: this.logger }); // Request tracking this.pendingRequests = new Map(); this.requestTimeout = options.requestTimeout || 30000; // Read-only methods for caching this.readOnlyMethods = new Set([ // Block methods 'eth_blockNumber', 'eth_getBlockByNumber', 'eth_getBlockByHash', 'eth_getBlockTransactionCountByNumber', 'eth_getBlockTransactionCountByHash', 'eth_getUncleCountByBlockNumber', 'eth_getUncleCountByBlockHash', 'eth_getUncleByBlockNumberAndIndex', 'eth_getUncleByBlockHashAndIndex', // Account methods 'eth_getBalance', 'eth_getCode', 'eth_getTransactionCount', 'eth_getStorageAt', // Transaction methods 'eth_getTransactionByHash', 'eth_getTransactionReceipt', 'eth_getTransactionByBlockNumberAndIndex', 'eth_getTransactionByBlockHashAndIndex', // Gas and fee methods 'eth_gasPrice', 'eth_feeHistory', 'eth_maxPriorityFeePerGas', // Contract interaction (read-only) 'eth_call', 'eth_createAccessList', // Network and node info 'eth_chainId', 'net_version', 'net_listening', 'net_peerCount', 'eth_protocolVersion', 'eth_syncing', 'eth_coinbase', 'eth_mining', 'eth_hashrate', 'eth_accounts', // Web3 methods 'web3_clientVersion', 'web3_sha3' ]); // Set up event handlers this.setupEventHandlers(); // Automatically connect on instantiation this.connect().catch(err => { this.logger.error('Failed to connect on instantiation:', err); }); } /** * Set up event handlers for components */ setupEventHandlers() { // Connection events this.connection.on('connected', () => { this.metrics.recordConnectionEvent('connect'); }); this.connection.on('disconnected', () => { this.metrics.recordConnectionEvent('disconnect'); }); this.connection.on('error', (error) => { this.metrics.recordConnectionEvent('error'); this.logger.error('Connection error:', error); }); this.connection.on('data', (data) => { this.handleIncomingData(data); }); // Batch processor events this.batchProcessor.on('batchReady', (batch) => { this.processBatch(batch); }); } /** * Handle incoming data from IPC connection */ handleIncomingData(data) { const responses = this.jsonParser.processData(data); for (const response of responses) { if (response.id && this.pendingRequests.has(response.id)) { const { resolve, reject, tracking, timeout } = this.pendingRequests.get(response.id); this.pendingRequests.delete(response.id); clearTimeout(timeout); if (response.error) { this.metrics.recordRequestFailure(tracking, new Error(response.error.message)); reject(new Error(response.error.message)); } else { this.metrics.recordRequestSuccess(tracking); // Cache successful read-only responses if (tracking.method && this.readOnlyMethods.has(tracking.method)) { const cacheKey = `${tracking.method}:${JSON.stringify(tracking.params)}`; this.cache.set(cacheKey, response.result); } resolve(response.result); } } } } /** * Process a batch of requests */ async processBatch({ batchId, requests, items }) { try { const requestJson = JSON.stringify(requests) + '\n'; this.connection.write(requestJson); } catch (error) { items.forEach(item => item.reject(error)); this.logger.error('Batch processing error:', error); } } /** * Connect to IPC endpoint */ async connect() { if (this.connection.isConnected) { return; } await this.connection.connect(); this.logger.log('Provider connected to IPC socket'); } /** * Ensure connection is established */ async ensureConnected() { if (!this.connection.isConnected) { await this.connect(); } } /** * Make an RPC request */ async request(methodOrPayload, params) { await this.ensureConnected(); let method, parameters; if (typeof methodOrPayload === 'object') { method = methodOrPayload.method; parameters = methodOrPayload.params || []; } else { method = methodOrPayload; parameters = params || []; } if (this.readOnlyMethods.has(method)) { const cacheKey = `${method}:${JSON.stringify(parameters)}`; const cached = this.cache.get(cacheKey); if (cached !== null) { return cached; } } const id = uuidv4(); const tracking = this.metrics.recordRequestStart(method, id); const request = { jsonrpc: '2.0', id, method, params: parameters }; return new Promise((resolve, reject) => { const timeout = setTimeout(() => { if (this.pendingRequests.has(id)) { this.pendingRequests.delete(id); this.metrics.recordRequestFailure(tracking, new Error('Request timeout'), 'timeout'); reject(new Error(`Request timeout after ${this.requestTimeout}ms`)); } }, this.requestTimeout); this.pendingRequests.set(id, { resolve, reject, tracking, timeout }); try { const requestJson = JSON.stringify(request) + '\n'; this.connection.write(requestJson); } catch (error) { this.pendingRequests.delete(id); clearTimeout(timeout); this.metrics.recordRequestFailure(tracking, error); reject(error); } }); } /** * Disconnect from IPC endpoint */ async disconnect() { if (this.connection.isConnected) { await this.connection.disconnect(); this.logger.log('Provider disconnected from IPC socket'); } } // Ethereum JSON-RPC method implementations async getBlockNumber() { const result = await this.request('eth_blockNumber'); return typeof result === 'string' ? parseInt(result, 16) : result; } async getBalance(address, blockTag = 'latest') { const result = await this.request('eth_getBalance', [address, blockTag]); return typeof result === 'string' ? BigInt(result).toString() : result; } async getTransactionCount(address, blockTag = 'latest') { const result = await this.request('eth_getTransactionCount', [address, blockTag]); return typeof result === 'string' ? parseInt(result, 16) : result; } // Helper methods toHex(value) { if (typeof value === 'string') { if (value.startsWith('0x')) return value; if (['latest', 'earliest', 'pending'].includes(value)) return value; } return '0x' + BigInt(value).toString(16); } fromHex(hexString) { return parseInt(hexString, 16); } } export default IPCProvider;