Files
DLE/backend/services/deployParamsService.js

531 lines
24 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Сервис для работы с параметрами деплоя DLE
* Copyright (c) 2024-2025 Тарабанов Александр Викторович
*/
const { Pool } = require('pg');
const logger = require('../utils/logger');
const encryptedDb = require('./encryptedDatabaseService');
class DeployParamsService {
constructor() {
this.pool = new Pool({
user: process.env.DB_USER || 'dapp_user',
host: process.env.DB_HOST || 'dapp-postgres',
database: process.env.DB_NAME || 'dapp_db',
password: process.env.DB_PASSWORD || 'dapp_password',
port: process.env.DB_PORT || 5432,
});
// Используем глобальный экземпляр encryptedDb
}
/**
* Сохраняет параметры деплоя в базу данных
* @param {string} deploymentId - Идентификатор деплоя
* @param {Object} params - Параметры деплоя
* @param {string} status - Статус деплоя
* @returns {Promise<Object>} - Сохраненные параметры
*/
async saveDeployParams(deploymentId, params, status = 'pending') {
try {
logger.info(`💾 Сохранение параметров деплоя в БД: ${deploymentId}`);
const dataToSave = {
deployment_id: deploymentId,
name: params.name,
symbol: params.symbol,
location: params.location,
coordinates: params.coordinates,
jurisdiction: params.jurisdiction,
oktmo: params.oktmo,
okved_codes: JSON.stringify(params.okved_codes || []),
kpp: params.kpp,
quorum_percentage: params.quorum_percentage,
initial_partners: JSON.stringify(params.initial_partners || []),
// initialAmounts в человекочитаемом формате, умножение на 1e18 происходит при деплое
initial_amounts: JSON.stringify(params.initial_amounts || []),
supported_chain_ids: JSON.stringify(params.supported_chain_ids || []),
current_chain_id: params.current_chain_id || 1, // По умолчанию Ethereum
logo_uri: params.logo_uri,
private_key: params.private_key, // Будет автоматически зашифрован
etherscan_api_key: params.etherscan_api_key,
auto_verify_after_deploy: params.auto_verify_after_deploy || false,
create2_salt: params.create2_salt,
rpc_urls: JSON.stringify(params.rpc_urls ? (Array.isArray(params.rpc_urls) ? params.rpc_urls : Object.values(params.rpc_urls)) : []),
initializer: params.initializer,
dle_address: params.dle_address,
modules_to_deploy: JSON.stringify(params.modules_to_deploy || []),
deployment_status: status
};
// Используем encryptedDb для автоматического шифрования
// Проверяем, существует ли уже запись с таким deployment_id
const existing = await this.getDeployParams(deploymentId);
const result = existing
? await encryptedDb.saveData('deploy_params', dataToSave, { deployment_id: deploymentId })
: await encryptedDb.saveData('deploy_params', dataToSave);
logger.info(`✅ Параметры деплоя сохранены в БД (с шифрованием): ${deploymentId}`);
return result;
} catch (error) {
logger.error(`❌ Ошибка при сохранении параметров деплоя: ${error.message}`);
throw error;
}
}
/**
* Получает параметры деплоя по идентификатору
* @param {string} deploymentId - Идентификатор деплоя
* @returns {Promise<Object|null>} - Параметры деплоя или null
*/
async getDeployParams(deploymentId) {
try {
logger.info(`📖 Получение параметров деплоя из БД: ${deploymentId}`);
// Используем encryptedDb для автоматического расшифрования
const result = await encryptedDb.getData('deploy_params', {
deployment_id: deploymentId
});
if (!result || result.length === 0) {
logger.warn(`⚠️ Параметры деплоя не найдены: ${deploymentId}`);
logger.warn(`🔍 Тип deploymentId: ${typeof deploymentId}`);
logger.warn(`🔍 Значение deploymentId: "${deploymentId}"`);
// Попробуем найти все записи для отладки
const allRecords = await encryptedDb.getData('deploy_params', {});
logger.warn(`🔍 Всего записей в deploy_params: ${allRecords?.length || 0}`);
if (allRecords && allRecords.length > 0) {
logger.warn(`🔍 Последние deployment_id: ${allRecords.map(r => r.deployment_id).slice(-3).join(', ')}`);
}
return null;
}
const params = result[0];
// PostgreSQL автоматически преобразует JSONB в объекты JavaScript
return {
...params,
okvedCodes: params.okved_codes || [],
initialPartners: params.initial_partners || [],
initialAmounts: params.initial_amounts || [],
supportedChainIds: params.supported_chain_ids || [],
rpcUrls: params.rpc_urls || [],
modulesToDeploy: params.modules_to_deploy || [],
CREATE2_SALT: params.create2_salt,
create2_salt: params.create2_salt, // Дублируем для совместимости
logoURI: params.logo_uri,
privateKey: params.private_key, // Автоматически расшифрован
etherscanApiKey: params.etherscan_api_key,
autoVerifyAfterDeploy: params.auto_verify_after_deploy,
dleAddress: params.dle_address,
deploymentStatus: params.deployment_status
};
} catch (error) {
logger.error(`❌ Ошибка при получении параметров деплоя: ${error.message}`);
throw error;
}
}
/**
* Получает параметры деплоя по адресу DLE
* @param {string} dleAddress - Адрес DLE контракта
* @returns {Promise<Object|null>} - Параметры деплоя или null
*/
async getDeployParamsByDleAddress(dleAddress) {
try {
logger.info(`📖 Поиск параметров деплоя по адресу DLE: ${dleAddress}`);
// Используем encryptedDb для поиска по адресу DLE
const result = await encryptedDb.getData('deploy_params', {
dle_address: dleAddress
});
if (!result || result.length === 0) {
logger.warn(`⚠️ Параметры деплоя не найдены для адреса: ${dleAddress}`);
return null;
}
// Возвращаем первый найденный результат
return result[0];
} catch (error) {
logger.error(`❌ Ошибка при поиске параметров деплоя по адресу: ${error.message}`);
throw error;
}
}
/**
* Обновляет статус деплоя
* @param {string} deploymentId - Идентификатор деплоя
* @param {string} status - Новый статус
* @param {string} dleAddress - Адрес задеплоенного контракта
* @returns {Promise<Object>} - Обновленные параметры
*/
async updateDeploymentStatus(deploymentId, status, result = null) {
try {
logger.info(`🔄 Обновление статуса деплоя: ${deploymentId} -> ${status}`);
// Подготавливаем данные для обновления
let dleAddress = null;
let deployResult = null;
if (result) {
logger.info(`🔍 [DEBUG] updateDeploymentStatus получил result:`, JSON.stringify(result, null, 2));
// Извлекаем адреса из результата деплоя
if (result.data && result.data.networks && result.data.networks.length > 0) {
// Берем первый адрес для обратной совместимости
dleAddress = result.data.networks[0].address;
logger.info(`✅ [DEBUG] Найден адрес в result.data.networks[0].address: ${dleAddress}`);
} else if (result.networks && result.networks.length > 0) {
// Берем первый адрес для обратной совместимости
dleAddress = result.networks[0].address;
logger.info(`✅ [DEBUG] Найден адрес в result.networks[0].address: ${dleAddress}`);
} else if (result.output) {
// Ищем адрес в тексте output - сначала пробуем найти JSON массив с адресами
const jsonArrayMatch = result.output.match(/\[[\s\S]*?"address":\s*"(0x[a-fA-F0-9]{40})"[\s\S]*?\]/);
if (jsonArrayMatch) {
dleAddress = jsonArrayMatch[1];
logger.info(`✅ [DEBUG] Найден адрес в JSON массиве result.output: ${dleAddress}`);
} else {
// Fallback: ищем адрес в тексте output (формат: "📍 Адрес: 0x...")
const addressMatch = result.output.match(/📍 Адрес: (0x[a-fA-F0-9]{40})/);
if (addressMatch) {
dleAddress = addressMatch[1];
logger.info(`✅ [DEBUG] Найден адрес в тексте result.output: ${dleAddress}`);
}
}
} else {
logger.warn(`⚠️ [DEBUG] Адрес не найден в результате деплоя`);
}
// Сохраняем полный результат деплоя (включая все адреса всех сетей)
deployResult = JSON.stringify(result);
}
const query = `
UPDATE deploy_params
SET deployment_status = $2, dle_address = $3, deploy_result = $4, updated_at = CURRENT_TIMESTAMP
WHERE deployment_id = $1
RETURNING *
`;
const queryResult = await this.pool.query(query, [deploymentId, status, dleAddress, deployResult]);
if (queryResult.rows.length === 0) {
throw new Error(`Параметры деплоя не найдены: ${deploymentId}`);
}
logger.info(`✅ Статус деплоя обновлен: ${deploymentId} -> ${status}`);
if (dleAddress) {
logger.info(`📍 Адрес DLE контракта: ${dleAddress}`);
}
return queryResult.rows[0];
} catch (error) {
logger.error(`❌ Ошибка при обновлении статуса деплоя: ${error.message}`);
throw error;
}
}
/**
* Получает последние параметры деплоя
* @param {number} limit - Количество записей
* @returns {Promise<Array>} - Список параметров деплоя
*/
async getLatestDeployParams(limit = 10) {
try {
logger.info(`📋 Получение последних параметров деплоя (лимит: ${limit})`);
// Используем encryptedDb для автоматического расшифрования
const result = await encryptedDb.getData('deploy_params', {}, limit, 'created_at DESC');
// PostgreSQL автоматически преобразует JSONB в объекты JavaScript
return result.map(row => ({
...row,
okvedCodes: row.okved_codes || [],
initialPartners: row.initial_partners || [],
initialAmounts: row.initial_amounts || [],
supportedChainIds: row.supported_chain_ids || [],
rpcUrls: row.rpc_urls || [],
modulesToDeploy: row.modules_to_deploy || [],
CREATE2_SALT: row.create2_salt,
create2_salt: row.create2_salt, // Дублируем для совместимости
logoURI: row.logo_uri,
privateKey: row.private_key, // Автоматически расшифрован
etherscanApiKey: row.etherscan_api_key,
autoVerifyAfterDeploy: row.auto_verify_after_deploy,
dleAddress: row.dle_address,
deploymentStatus: row.deployment_status
}));
} catch (error) {
logger.error(`❌ Ошибка при получении последних параметров деплоя: ${error.message}`);
throw error;
}
}
/**
* Удаляет параметры деплоя по deployment_id (только для отладки)
* @param {string} deploymentId - Идентификатор деплоя
* @returns {Promise<boolean>} - Результат удаления
*/
async deleteDeployParams(deploymentId) {
try {
logger.info(`🗑️ Удаление параметров деплоя: ${deploymentId}`);
const query = 'DELETE FROM deploy_params WHERE deployment_id = $1';
const result = await this.pool.query(query, [deploymentId]);
const deleted = result.rowCount > 0;
if (deleted) {
logger.info(`✅ Параметры деплоя удалены: ${deploymentId}`);
} else {
logger.warn(`⚠️ Параметры деплоя не найдены: ${deploymentId}`);
}
return deleted;
} catch (error) {
logger.error(`❌ Ошибка при удалении параметров деплоя: ${error.message}`);
throw error;
}
}
/**
* Получить контракты по chainId
*/
async getContractsByChainId(chainId) {
try {
console.log(`[DeployParamsService] Ищем контракты с current_chain_id: ${chainId}`);
const query = `
SELECT
deployment_id,
name,
dle_address,
current_chain_id,
supported_chain_ids,
created_at
FROM deploy_params
WHERE current_chain_id = $1 AND dle_address IS NOT NULL
ORDER BY created_at DESC
`;
const result = await this.pool.query(query, [chainId]);
console.log(`[DeployParamsService] Найдено контрактов: ${result.rows.length}`);
return result.rows.map(row => ({
deploymentId: row.deployment_id,
name: row.name,
dleAddress: row.dle_address,
currentChainId: row.current_chain_id,
supportedChainIds: row.supported_chain_ids,
createdAt: row.created_at
}));
} catch (error) {
console.error(`[DeployParamsService] Ошибка поиска контрактов по chainId:`, error);
throw error;
}
}
/**
* Получает все деплои
* @param {number} limit - Количество записей
* @returns {Promise<Array>} - Список всех деплоев
*/
async getAllDeployments(limit = 50) {
try {
logger.info(`📋 Получение всех деплоев (лимит: ${limit})`);
// Используем encryptedDb для автоматического расшифрования
const result = await encryptedDb.getData('deploy_params', {}, limit, 'created_at DESC');
return result.map(row => {
// Парсим deployResult для извлечения адресов всех сетей
let deployedNetworks = [];
console.log(`🔍 [DEBUG] Processing deployment ${row.deployment_id}, deploy_result exists:`, !!row.deploy_result);
console.log(`🔍 [DEBUG] deploy_result type:`, typeof row.deploy_result);
if (row.deploy_result) {
try {
const deployResult = typeof row.deploy_result === 'string'
? JSON.parse(row.deploy_result)
: row.deploy_result;
console.log(`🔍 [DEBUG] deployResult keys:`, Object.keys(deployResult));
console.log(`🔍 [DEBUG] deployResult.output exists:`, !!deployResult.output);
console.log(`🔍 [DEBUG] deployResult.data exists:`, !!deployResult.data);
console.log(`🔍 [DEBUG] deployResult.networks exists:`, !!deployResult.networks);
if (deployResult.error) {
console.log(`🔍 [DEBUG] deployResult.error:`, deployResult.error);
}
// Функция для получения правильного названия сети
const getNetworkName = (chainId) => {
const networkNames = {
1: 'Ethereum Mainnet',
11155111: 'Sepolia',
17000: 'Holesky',
421614: 'Arbitrum Sepolia',
84532: 'Base Sepolia',
137: 'Polygon',
56: 'BSC',
42161: 'Arbitrum One'
};
return networkNames[chainId] || `Chain ${chainId}`;
};
// Функция для загрузки ABI для конкретной сети
const loadABIForNetwork = (chainId) => {
try {
const fs = require('fs');
const path = require('path');
const abiPath = path.join(__dirname, '../../../frontend/src/utils/dle-abi.js');
if (fs.existsSync(abiPath)) {
const abiContent = fs.readFileSync(abiPath, 'utf8');
// Используем более простое регулярное выражение
const abiMatch = abiContent.match(/export const DLE_ABI = (\[[\s\S]*?\]);/);
if (abiMatch) {
// Попробуем исправить JSON, заменив проблемные символы
let abiText = abiMatch[1];
// Убираем лишние запятые в конце
abiText = abiText.replace(/,(\s*[}\]])/g, '$1');
try {
return JSON.parse(abiText);
} catch (parseError) {
console.warn(`⚠️ Ошибка парсинга ABI JSON для сети ${chainId}:`, parseError.message);
// Возвращаем пустой массив как fallback
return [];
}
}
}
} catch (abiError) {
console.warn(`⚠️ Ошибка загрузки ABI для сети ${chainId}:`, abiError.message);
}
return null;
};
// Извлекаем адреса из результата деплоя
if (deployResult.data && deployResult.data.networks) {
deployedNetworks = deployResult.data.networks.map(network => ({
chainId: network.chainId,
address: network.address,
networkName: network.networkName || getNetworkName(network.chainId),
abi: loadABIForNetwork(network.chainId) // ABI для каждой сети отдельно
}));
} else if (deployResult.networks) {
deployedNetworks = deployResult.networks.map(network => ({
chainId: network.chainId,
address: network.address,
networkName: network.networkName || getNetworkName(network.chainId),
abi: loadABIForNetwork(network.chainId) // ABI для каждой сети отдельно
}));
} else if (deployResult.output) {
console.log(`🔍 [DEBUG] Processing deployResult.output`);
// Извлекаем адреса из текста output
const output = deployResult.output;
const addressMatches = output.match(/📍 Адрес: (0x[a-fA-F0-9]{40})/g);
const chainIdMatches = output.match(/chainId: (\d+)/g);
// Альтернативный поиск по названиям сетей
const networkMatches = output.match(/🔍 Верификация в сети (\w+) \(chainId: (\d+)\)/g);
console.log(`🔍 [DEBUG] addressMatches:`, addressMatches);
console.log(`🔍 [DEBUG] chainIdMatches:`, chainIdMatches);
console.log(`🔍 [DEBUG] networkMatches:`, networkMatches);
if (networkMatches && networkMatches.length > 0) {
// Используем networkMatches для более точного парсинга
deployedNetworks = networkMatches.map((match) => {
const [, networkName, chainIdStr] = match.match(/🔍 Верификация в сети (\w+) \(chainId: (\d+)\)/);
const chainId = parseInt(chainIdStr);
// Ищем адрес для этой сети в output
const addressRegex = new RegExp(`🔍 Верификация в сети ${networkName} \\(chainId: ${chainId}\\)\\n📍 Адрес: (0x[a-fA-F0-9]{40})`);
const addressMatch = output.match(addressRegex);
const address = addressMatch ? addressMatch[1] : '0x0000000000000000000000000000000000000000';
return {
chainId: chainId,
address: address,
networkName: getNetworkName(chainId),
abi: loadABIForNetwork(chainId)
};
});
console.log(`🔍 [DEBUG] deployedNetworks created from networkMatches:`, deployedNetworks);
} else if (addressMatches && chainIdMatches) {
deployedNetworks = addressMatches.map((match, index) => {
const address = match.match(/📍 Адрес: (0x[a-fA-F0-9]{40})/)[1];
const chainId = chainIdMatches[index] ? parseInt(chainIdMatches[index].match(/chainId: (\d+)/)[1]) : null;
return {
chainId: chainId,
address: address,
networkName: chainId ? getNetworkName(chainId) : `Network ${index + 1}`,
abi: loadABIForNetwork(chainId) // ABI для каждой сети отдельно
};
});
console.log(`🔍 [DEBUG] deployedNetworks created:`, deployedNetworks);
} else {
console.log(`🔍 [DEBUG] No matches found - addressMatches:`, !!addressMatches, 'chainIdMatches:', !!chainIdMatches);
}
}
} catch (error) {
logger.warn(`⚠️ Ошибка парсинга deployResult для ${row.deployment_id}: ${error.message}`);
}
}
return {
deploymentId: row.deployment_id,
name: row.name,
symbol: row.symbol,
location: row.location,
coordinates: row.coordinates,
jurisdiction: row.jurisdiction,
oktmo: row.oktmo,
okvedCodes: row.okved_codes || [],
kpp: row.kpp,
quorumPercentage: row.quorum_percentage,
initialPartners: row.initial_partners || [],
initialAmounts: row.initial_amounts || [],
supportedChainIds: row.supported_chain_ids || [],
currentChainId: row.current_chain_id,
logoURI: row.logo_uri,
etherscanApiKey: row.etherscan_api_key,
autoVerifyAfterDeploy: row.auto_verify_after_deploy,
create2Salt: row.create2_salt,
rpcUrls: row.rpc_urls || [],
initializer: row.initializer,
dleAddress: row.dle_address,
modulesToDeploy: row.modules_to_deploy || [],
deploymentStatus: row.deployment_status,
deployResult: row.deploy_result,
deployedNetworks: deployedNetworks, // Добавляем адреса всех сетей
createdAt: row.created_at,
updatedAt: row.updated_at
};
});
} catch (error) {
logger.error(`❌ Ошибка при получении всех деплоев: ${error.message}`);
throw error;
}
}
/**
* Закрывает соединение с базой данных
*/
async close() {
try {
await this.pool.end();
logger.info('🔌 Соединение с базой данных закрыто');
} catch (error) {
logger.error(`❌ Ошибка при закрытии соединения с БД: ${error.message}`);
}
}
}
module.exports = DeployParamsService;