feat: новая функция
This commit is contained in:
@@ -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 напрямую
|
||||
|
||||
87
backend/utils/loadRpcFromDatabase.js
Normal file
87
backend/utils/loadRpcFromDatabase.js
Normal 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 };
|
||||
@@ -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');
|
||||
|
||||
// Принудительно обновляем кэш
|
||||
|
||||
105
backend/utils/proxyManager.js
Normal file
105
backend/utils/proxyManager.js
Normal 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()
|
||||
};
|
||||
@@ -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() вместо этого
|
||||
|
||||
Reference in New Issue
Block a user