feat: новая функция

This commit is contained in:
2025-10-15 21:43:18 +03:00
parent 0e028bc722
commit e0300480e1
32 changed files with 972 additions and 439 deletions

View File

@@ -93,26 +93,26 @@ async function getBalance(provider, address) {
/**
* Создает RPC соединение с retry логикой
* @param {string} rpcUrl - URL RPC
* @param {number} chainId - ID цепочки
* @param {string} privateKey - Приватный ключ
* @param {Object} options - Опции соединения
* @returns {Promise<Object>} - {provider, wallet, network}
*/
async function createRPCConnection(rpcUrl, privateKey, options = {}) {
async function createRPCConnection(chainId, privateKey, options = {}) {
const rpcManager = new RPCConnectionManager();
return await rpcManager.createConnection(rpcUrl, privateKey, options);
return await rpcManager.createConnection(chainId, privateKey, options);
}
/**
* Создает множественные RPC соединения с обработкой ошибок
* @param {Array} rpcUrls - Массив RPC URL
* @param {Array} chainIds - Массив chain ID
* @param {string} privateKey - Приватный ключ
* @param {Object} options - Опции соединения
* @returns {Promise<Array>} - Массив успешных соединений
*/
async function createMultipleRPCConnections(rpcUrls, privateKey, options = {}) {
async function createMultipleRPCConnections(chainIds, privateKey, options = {}) {
const rpcManager = new RPCConnectionManager();
return await rpcManager.createMultipleConnections(rpcUrls, privateKey, options);
return await rpcManager.createMultipleConnections(chainIds, privateKey, options);
}
// sendTransactionWithRetry функция удалена - используем RPCConnectionManager напрямую

View File

@@ -0,0 +1,87 @@
/**
* Загрузка RPC URL из базы данных и установка в переменные окружения
* Copyright (c) 2024-2025 Тарабанов Александр Викторович
*/
// Убрано - не нужен для загрузки RPC
/**
* Загружает RPC URL из базы данных и устанавливает их в переменные окружения
*/
async function loadRpcFromDatabase() {
try {
console.log('[RPC Loader] Загружаем RPC URL из базы данных...');
// Получаем все RPC провайдеры из базы данных
const rpcService = require('../services/rpcProviderService');
const providers = await rpcService.getAllRpcProviders();
if (providers.length === 0) {
console.warn('[RPC Loader] В базе данных нет RPC провайдеров');
return;
}
console.log(`[RPC Loader] Найдено ${providers.length} RPC провайдеров в базе данных`);
// Устанавливаем переменные окружения для каждой сети
for (const provider of providers) {
const chainId = provider.chain_id;
const rpcUrl = provider.rpc_url;
if (!rpcUrl) {
console.warn(`[RPC Loader] RPC URL не найден для chain_id ${chainId}`);
continue;
}
// Устанавливаем переменные окружения в зависимости от chain_id
switch (chainId) {
case 1: // Ethereum Mainnet
process.env.MAINNET_RPC_URL = rpcUrl;
console.log(`[RPC Loader] Установлен MAINNET_RPC_URL: ${rpcUrl}`);
break;
case 11155111: // Sepolia
process.env.SEPOLIA_RPC_URL = rpcUrl;
console.log(`[RPC Loader] Установлен SEPOLIA_RPC_URL: ${rpcUrl}`);
break;
case 17000: // Holesky
process.env.HOLESKY_RPC_URL = rpcUrl;
console.log(`[RPC Loader] Установлен HOLESKY_RPC_URL: ${rpcUrl}`);
break;
case 421614: // Arbitrum Sepolia
process.env.ARBITRUM_SEPOLIA_RPC_URL = rpcUrl;
console.log(`[RPC Loader] Установлен ARBITRUM_SEPOLIA_RPC_URL: ${rpcUrl}`);
break;
case 84532: // Base Sepolia
process.env.BASE_SEPOLIA_RPC_URL = rpcUrl;
console.log(`[RPC Loader] Установлен BASE_SEPOLIA_RPC_URL: ${rpcUrl}`);
break;
case 42161: // Arbitrum One
process.env.ARBITRUM_ONE_RPC_URL = rpcUrl;
console.log(`[RPC Loader] Установлен ARBITRUM_ONE_RPC_URL: ${rpcUrl}`);
break;
case 8453: // Base
process.env.BASE_RPC_URL = rpcUrl;
console.log(`[RPC Loader] Установлен BASE_RPC_URL: ${rpcUrl}`);
break;
case 137: // Polygon
process.env.POLYGON_RPC_URL = rpcUrl;
console.log(`[RPC Loader] Установлен POLYGON_RPC_URL: ${rpcUrl}`);
break;
case 56: // BSC
process.env.BSC_RPC_URL = rpcUrl;
console.log(`[RPC Loader] Установлен BSC_RPC_URL: ${rpcUrl}`);
break;
default:
console.log(`[RPC Loader] Неизвестный chain_id ${chainId}, пропускаем`);
}
}
console.log('[RPC Loader] ✅ RPC URL успешно загружены из базы данных');
} catch (error) {
console.error('[RPC Loader] ❌ Ошибка загрузки RPC URL из базы данных:', error);
throw error;
}
}
module.exports = { loadRpcFromDatabase };

View File

@@ -23,10 +23,20 @@ class NonceManager {
async getNonce(address, rpcUrl, chainId, options = {}) {
const { timeout = 15000, maxRetries = 5 } = options; // Увеличиваем таймаут и попытки
// КРИТИЧЕСКАЯ ПРОВЕРКА: логируем входящие параметры
console.log(`[NonceManager] getNonce вызван с параметрами: address=${address}, rpcUrl=${rpcUrl}, chainId=${chainId}`);
// КРИТИЧЕСКАЯ ПРОВЕРКА: если rpcUrl содержит 127.0.0.1:8545, это ошибка!
if (rpcUrl && rpcUrl.includes('127.0.0.1:8545')) {
console.error(`[NonceManager] ❌ КРИТИЧЕСКАЯ ОШИБКА: Получен неправильный rpcUrl: ${rpcUrl} для chainId ${chainId}`);
throw new Error(`Получен неправильный rpcUrl: ${rpcUrl} для chainId ${chainId}`);
}
const cacheKey = `${address}-${chainId}`;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
console.log(`[NonceManager] Попытка ${attempt}: создаем JsonRpcProvider с rpcUrl: ${rpcUrl}`);
const provider = new ethers.JsonRpcProvider(rpcUrl, undefined, {
polling: false, // Отключаем polling для более быстрого получения nonce
staticNetwork: true
@@ -204,8 +214,8 @@ class NonceManager {
console.warn(`[NonceManager] networkLoader недоступен для chainId ${chainId}, используем fallback: ${error.message}`);
}
// Всегда добавляем fallback RPC для надежности
const fallbackRPCs = this.getFallbackRPCs(chainId);
// Всегда добавляем fallback RPC для надежности ИЗ БАЗЫ ДАННЫХ
const fallbackRPCs = await this.getFallbackRPCs(chainId);
for (const fallbackRpc of fallbackRPCs) {
if (!rpcUrls.includes(fallbackRpc)) {
rpcUrls.push(fallbackRpc);
@@ -217,34 +227,31 @@ class NonceManager {
}
/**
* Получить список fallback RPC для сети
* Получить список fallback RPC для сети ИЗ БАЗЫ ДАННЫХ
* @param {number} chainId - ID сети
* @returns {Array} - Массив RPC URL
* @returns {Array} - Массив RPC URL из базы данных
*/
getFallbackRPCs(chainId) {
const fallbackRPCs = {
1: [ // Mainnet
'https://eth.llamarpc.com',
'https://rpc.ankr.com/eth',
'https://ethereum.publicnode.com'
],
11155111: [ // Sepolia
'https://rpc.sepolia.org',
process.env.SEPOLIA_INFURA_URL || 'https://sepolia.infura.io/v3/YOUR_INFURA_KEY'
],
17000: [ // Holesky
'https://ethereum-holesky.publicnode.com',
process.env.HOLESKY_INFURA_URL || 'https://holesky.infura.io/v3/YOUR_INFURA_KEY'
],
421614: [ // Arbitrum Sepolia
'https://sepolia-rollup.arbitrum.io/rpc'
],
84532: [ // Base Sepolia
'https://sepolia.base.org'
]
};
return fallbackRPCs[chainId] || [];
async getFallbackRPCs(chainId) {
try {
// Получаем ВСЕ RPC провайдеры для данной сети из базы данных
const rpcService = require('../services/rpcProviderService');
const providers = await rpcService.getAllRpcProviders();
// Фильтруем по chain_id
const networkProviders = providers.filter(p => p.chain_id === chainId);
if (networkProviders.length === 0) {
console.warn(`[NonceManager] Нет RPC провайдеров в базе данных для chain_id: ${chainId}`);
return [];
}
// Возвращаем только RPC URL из базы данных
return networkProviders.map(p => p.rpc_url).filter(url => url);
} catch (error) {
console.error(`[NonceManager] Ошибка получения RPC из базы данных для chain_id ${chainId}:`, error);
return [];
}
}
/**
@@ -276,7 +283,7 @@ class NonceManager {
const cacheKey = `${address}-${chainId}`;
try {
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcService.getRpcUrlByChainId(chainId));
const networkNonce = await provider.getTransactionCount(address, 'pending');
// Принудительно обновляем кэш

View File

@@ -0,0 +1,105 @@
/**
* Менеджер прокси настроек для RPC провайдеров
* Copyright (c) 2024-2025 Тарабанов Александр Викторович
*/
const rpcProviderService = require('../services/rpcProviderService');
class ProxyManager {
constructor() {
this.initialized = false;
}
/**
* Инициализация менеджера прокси
*/
async initialize() {
if (this.initialized) return;
try {
await this.configureNoProxyFromRpcProviders();
this.initialized = true;
console.log('[ProxyManager] ✅ Инициализация завершена');
} catch (error) {
console.error('[ProxyManager] ❌ Ошибка инициализации:', error.message);
throw error;
}
}
/**
* Настройка NO_PROXY на основе RPC провайдеров из базы данных
*/
async configureNoProxyFromRpcProviders() {
try {
const providers = await rpcProviderService.getAllRpcProviders();
const rpcDomains = providers
.map(provider => provider.rpc_url)
.filter(url => url && url.startsWith('http'))
.map(url => {
try {
const urlObj = new URL(url);
return urlObj.hostname;
} catch (e) {
console.warn(`[ProxyManager] Неверный URL: ${url}`, e.message);
return null;
}
})
.filter(hostname => hostname)
.filter((hostname, index, array) => array.indexOf(hostname) === index); // убираем дубликаты
if (rpcDomains.length > 0) {
const existingNoProxy = process.env.NO_PROXY || '';
// Добавляем RPC домены к существующему NO_PROXY
const newDomains = rpcDomains.filter(domain => !existingNoProxy.includes(domain));
if (newDomains.length > 0) {
process.env.NO_PROXY = existingNoProxy ? `${existingNoProxy},${newDomains.join(',')}` : newDomains.join(',');
console.log('[ProxyManager] ✅ Добавлены RPC домены в NO_PROXY:', newDomains.join(', '));
console.log('[ProxyManager] 📋 Обновленный NO_PROXY:', process.env.NO_PROXY);
} else {
console.log('[ProxyManager] Все RPC домены уже в NO_PROXY');
}
} else {
console.warn('[ProxyManager] ⚠️ Не найдено RPC провайдеров для настройки NO_PROXY');
}
} catch (error) {
console.error('[ProxyManager] ❌ Не удалось загрузить RPC провайдеры для NO_PROXY:', error.message);
throw error;
}
}
/**
* Проверить текущие настройки прокси
*/
getProxyStatus() {
return {
httpProxy: process.env.HTTP_PROXY || null,
httpsProxy: process.env.HTTPS_PROXY || null,
noProxy: process.env.NO_PROXY || null,
initialized: this.initialized
};
}
/**
* Принудительно обновить настройки NO_PROXY
*/
async refresh() {
this.initialized = false;
await this.initialize();
}
}
// Создаем singleton
const proxyManager = new ProxyManager();
module.exports = {
ProxyManager,
proxyManager,
// Экспортируем методы для удобства
initialize: () => proxyManager.initialize(),
configureNoProxyFromRpcProviders: () => proxyManager.configureNoProxyFromRpcProviders(),
getProxyStatus: () => proxyManager.getProxyStatus(),
refresh: () => proxyManager.refresh()
};

View File

@@ -5,6 +5,7 @@
const { ethers } = require('ethers');
const logger = require('./logger');
const rpcService = require('../services/rpcProviderService');
class RPCConnectionManager {
constructor() {
@@ -19,13 +20,26 @@ class RPCConnectionManager {
/**
* Создает RPC соединение с retry логикой
* @param {string} rpcUrl - URL RPC
* @param {number} chainId - ID цепочки
* @param {string} privateKey - Приватный ключ
* @param {Object} options - Опции соединения
* @returns {Promise<Object>} - {provider, wallet, network}
*/
async createConnection(rpcUrl, privateKey, options = {}) {
async createConnection(chainId, privateKey, options = {}) {
const config = { ...this.retryConfig, ...options };
const rpcUrl = await rpcService.getRpcUrlByChainId(chainId);
logger.info(`[RPC_MANAGER] Получен RPC URL для chainId ${chainId}: ${rpcUrl}`);
// КРИТИЧЕСКАЯ ПРОВЕРКА: если rpcUrl содержит 127.0.0.1:8545, это ошибка!
if (rpcUrl && rpcUrl.includes('127.0.0.1:8545')) {
logger.error(`[RPC_MANAGER] ❌ КРИТИЧЕСКАЯ ОШИБКА: Получен неправильный RPC URL: ${rpcUrl} для chainId ${chainId}`);
throw new Error(`Получен неправильный RPC URL: ${rpcUrl} для chainId ${chainId}`);
}
if (!rpcUrl) {
throw new Error(`RPC URL не найден для chainId ${chainId}`);
}
const connectionKey = `${rpcUrl}_${privateKey}`;
// Проверяем кэш
@@ -33,7 +47,8 @@ class RPCConnectionManager {
const cached = this.connections.get(connectionKey);
if (Date.now() - cached.timestamp < 60000) { // 1 минута кэш
logger.info(`[RPC_MANAGER] Используем кэшированное соединение: ${rpcUrl}`);
return cached.connection;
// Убеждаемся, что кэшированное соединение содержит rpcUrl
return { ...cached.connection, rpcUrl };
}
}
@@ -56,7 +71,7 @@ class RPCConnectionManager {
const wallet = new ethers.Wallet(privateKey, provider);
const connection = { provider, wallet, network };
const connection = { provider, wallet, network, rpcUrl };
// Кэшируем соединение
this.connections.set(connectionKey, {
@@ -84,21 +99,21 @@ class RPCConnectionManager {
/**
* Создает множественные RPC соединения с обработкой ошибок
* @param {Array} rpcUrls - Массив RPC URL
* @param {Array} chainIds - Массив chain ID
* @param {string} privateKey - Приватный ключ
* @param {Object} options - Опции соединения
* @returns {Promise<Array>} - Массив успешных соединений
*/
async createMultipleConnections(rpcUrls, privateKey, options = {}) {
logger.info(`[RPC_MANAGER] Создаем ${rpcUrls.length} RPC соединений...`);
async createMultipleConnections(chainIds, privateKey, options = {}) {
logger.info(`[RPC_MANAGER] Создаем ${chainIds.length} RPC соединений...`);
const connectionPromises = rpcUrls.map(async (rpcUrl, index) => {
const connectionPromises = chainIds.map(async (chainId, index) => {
try {
const connection = await this.createConnection(rpcUrl, privateKey, options);
return { index, rpcUrl, ...connection, success: true };
const connection = await this.createConnection(chainId, privateKey, options);
return { index, chainId, ...connection, success: true };
} catch (error) {
logger.error(`[RPC_MANAGER] ❌ Соединение ${index + 1} failed: ${rpcUrl} - ${error.message}`);
return { index, rpcUrl, error: error.message, success: false };
logger.error(`[RPC_MANAGER] ❌ Соединение ${index + 1} failed: chainId ${chainId} - ${error.message}`);
return { index, chainId, error: error.message, success: false };
}
});
@@ -106,10 +121,10 @@ class RPCConnectionManager {
const successful = results.filter(r => r.success);
const failed = results.filter(r => !r.success);
logger.info(`[RPC_MANAGER] ✅ Успешных соединений: ${successful.length}/${rpcUrls.length}`);
logger.info(`[RPC_MANAGER] ✅ Успешных соединений: ${successful.length}/${chainIds.length}`);
if (failed.length > 0) {
logger.warn(`[RPC_MANAGER] ⚠️ Неудачных соединений: ${failed.length}`);
failed.forEach(f => logger.warn(`[RPC_MANAGER] - ${f.rpcUrl}: ${f.error}`));
failed.forEach(f => logger.warn(`[RPC_MANAGER] - ChainId ${f.chainId}: ${f.error}`));
}
if (successful.length === 0) {
@@ -183,13 +198,25 @@ class RPCConnectionManager {
'ENOTFOUND',
'ETIMEDOUT',
'RPC timeout',
'Transaction timeout'
'Transaction timeout',
'ECONNREFUSED',
'ENETUNREACH',
'EHOSTUNREACH'
];
const errorMessage = error.message.toLowerCase();
return retryableErrors.some(retryableError =>
const isRetryable = retryableErrors.some(retryableError =>
errorMessage.includes(retryableError.toLowerCase())
);
// Логируем информацию об ошибке для диагностики
if (isRetryable) {
logger.warn(`[RPC_MANAGER] Повторяемая ошибка: ${error.message}`);
} else {
logger.error(`[RPC_MANAGER] Неповторяемая ошибка: ${error.message}`);
}
return isRetryable;
}
// getNonceWithRetry функция удалена - используем nonceManager.getNonceWithRetry() вместо этого