ваше сообщение коммита

This commit is contained in:
2025-08-16 22:12:00 +03:00
parent fe0dc6509b
commit 1d636c113f
28 changed files with 6809 additions and 525 deletions

View File

@@ -95,6 +95,7 @@ const dleTokensRoutes = require('./routes/dleTokens'); // Функции ток
const dleAnalyticsRoutes = require('./routes/dleAnalytics'); // Аналитика и история
const dleMultichainRoutes = require('./routes/dleMultichain'); // Мультичейн функции
const dleHistoryRoutes = require('./routes/dleHistory'); // Расширенная история
const systemRoutes = require('./routes/system'); // Добавляем импорт маршрутов системного мониторинга
const app = express();
@@ -233,6 +234,7 @@ app.use('/api/identities', identitiesRoutes);
app.use('/api/rag', ragRoutes); // Подключаем роут
app.use('/api/monitoring', monitoringRoutes);
app.use('/api/pages', pagesRoutes); // Подключаем роутер страниц
app.use('/api/system', systemRoutes); // Добавляем маршрут системного мониторинга
const nonceStore = new Map(); // или любая другая реализация хранилища nonce

View File

@@ -1,4 +1,4 @@
{
"_format": "hh-sol-dbg-1",
"buildInfo": "../../build-info/ab387c71734b3d3e5e7817d328027586.json"
"buildInfo": "../../build-info/aa0034b410e4fbe1d1ff90369d480540.json"
}

File diff suppressed because one or more lines are too long

View File

@@ -2,8 +2,8 @@
"_format": "hh-sol-cache-2",
"files": {
"/home/alex/Digital_Legal_Entity(DLE)/backend/contracts/DLE.sol": {
"lastModificationDate": 1755280436490,
"contentHash": "f676e9964a39b0fccdc62a9114266863",
"lastModificationDate": 1755366617069,
"contentHash": "47d6b51ed0025b36c50649b175745512",
"sourceName": "contracts/DLE.sol",
"solcConfig": {
"version": "0.8.20",

View File

@@ -20,6 +20,12 @@ import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
* @title DLE (Digital Legal Entity)
* @dev Основной контракт DLE с модульной архитектурой, Single-Chain Governance
* и безопасной мульти-чейн синхронизацией без сторонних мостов (через подписи холдеров).
*
* КЛЮЧЕВЫЕ ОСОБЕННОСТИ:
* - Прямые переводы токенов ЗАБЛОКИРОВАНЫ (transfer, transferFrom, approve)
* - Перевод токенов возможен ТОЛЬКО через governance предложения
* - Токены служат только для голосования и управления DLE
* - Все операции с токенами требуют коллективного решения
*/
contract DLE is ERC20, ERC20Permit, ERC20Votes, ReentrancyGuard {
using ECDSA for bytes32;
@@ -112,6 +118,7 @@ contract DLE is ERC20, ERC20Permit, ERC20Votes, ReentrancyGuard {
event DLEInfoUpdated(string name, string symbol, string location, string coordinates, uint256 jurisdiction, string[] okvedCodes, uint256 kpp);
event QuorumPercentageUpdated(uint256 oldQuorumPercentage, uint256 newQuorumPercentage);
event CurrentChainIdUpdated(uint256 oldChainId, uint256 newChainId);
event TokensTransferredByGovernance(address indexed recipient, uint256 amount);
// EIP712 typehash для подписи одобрения исполнения предложения в целевой сети
// ExecutionApproval(uint256 proposalId, bytes32 operationHash, uint256 chainId, uint256 snapshotTimepoint)
@@ -531,6 +538,10 @@ contract DLE is ERC20, ERC20Permit, ERC20Votes, ReentrancyGuard {
} else if (selector == bytes4(keccak256("_removeSupportedChain(uint256)"))) {
(uint256 chainIdToRemove) = abi.decode(data, (uint256));
_removeSupportedChain(chainIdToRemove);
} else if (selector == bytes4(keccak256("_transferTokens(address,uint256)"))) {
// Операция перевода токенов через governance
(address recipient, uint256 amount) = abi.decode(data, (address, uint256));
_transferTokens(recipient, amount);
} else if (selector == bytes4(keccak256("offchainAction(bytes32,string,bytes32)"))) {
// Оффчейн операция для приложения: идентификатор, тип, хеш полезной нагрузки
// (bytes32 actionId, string memory kind, bytes32 payloadHash) = abi.decode(data, (bytes32, string, bytes32));
@@ -604,6 +615,22 @@ contract DLE is ERC20, ERC20Permit, ERC20Votes, ReentrancyGuard {
emit CurrentChainIdUpdated(oldChainId, _newChainId);
}
/**
* @dev Перевести токены через governance (от имени DLE)
* @param _recipient Адрес получателя
* @param _amount Количество токенов для перевода
*/
function _transferTokens(address _recipient, uint256 _amount) internal {
require(_recipient != address(0), "Cannot transfer to zero address");
require(_amount > 0, "Amount must be positive");
require(balanceOf(address(this)) >= _amount, "Insufficient DLE balance");
// Переводим токены от имени DLE (address(this))
_transfer(address(this), _recipient, _amount);
emit TokensTransferredByGovernance(_recipient, _amount);
}
/**
* @dev Создать предложение о добавлении модуля
* @param _description Описание предложения
@@ -896,4 +923,38 @@ contract DLE is ERC20, ERC20Permit, ERC20Votes, ReentrancyGuard {
require(delegator == delegatee, "Delegation disabled");
super._delegate(delegator, delegatee);
}
// ===== Блокировка прямых переводов токенов =====
// Токены DLE могут быть переведены только через governance
/**
* @dev Блокирует прямые переводы токенов
* @param to Адрес получателя (не используется)
* @param amount Количество токенов (не используется)
* @return Всегда возвращает false
*/
function transfer(address to, uint256 amount) public override returns (bool) {
revert("Direct transfers disabled. Use governance proposals for token transfers.");
}
/**
* @dev Блокирует прямые переводы токенов через approve/transferFrom
* @param from Адрес отправителя (не используется)
* @param to Адрес получателя (не используется)
* @param amount Количество токенов (не используется)
* @return Всегда возвращает false
*/
function transferFrom(address from, address to, uint256 amount) public override returns (bool) {
revert("Direct transfers disabled. Use governance proposals for token transfers.");
}
/**
* @dev Блокирует прямые разрешения на перевод токенов
* @param spender Адрес, которому разрешается тратить токены (не используется)
* @param amount Количество токенов (не используется)
* @return Всегда возвращает false
*/
function approve(address spender, uint256 amount) public override returns (bool) {
revert("Direct approvals disabled. Use governance proposals for token transfers.");
}
}

View File

@@ -30,8 +30,39 @@ let pool = new Pool({
user: process.env.DB_USER || 'dapp_user',
password: process.env.DB_PASSWORD,
ssl: false,
// Настройки для предотвращения утечек памяти
max: 10, // Максимальное количество клиентов в пуле
min: 0, // Минимальное количество клиентов в пуле
idleTimeoutMillis: 30000, // Время жизни неактивного клиента (30 сек)
connectionTimeoutMillis: 2000, // Таймаут подключения (2 сек)
maxUses: 7500, // Максимальное количество использований клиента
allowExitOnIdle: true, // Разрешить выход при отсутствии активных клиентов
});
// Увеличиваем лимит обработчиков событий для предотвращения предупреждений
pool.setMaxListeners(20);
// Добавляем обработчики для правильного закрытия пула
pool.on('error', (err) => {
console.error('Unexpected error on idle client', err);
process.exit(-1);
});
// Обработчик для очистки при завершении процесса
process.on('SIGINT', async () => {
console.log('Closing database pool...');
await pool.end();
process.exit(0);
});
process.on('SIGTERM', async () => {
console.log('Closing database pool...');
await pool.end();
process.exit(0);
});
console.log('Пул создан:', pool.options || pool);
// Проверяем подключение к базе данных
pool.query('SELECT NOW()')
.then(res => {
@@ -41,8 +72,6 @@ pool.query('SELECT NOW()')
console.error('Ошибка подключения к базе данных:', err);
});
console.log('Пул создан:', pool.options || pool);
function getPool() {
return pool;
}
@@ -69,10 +98,11 @@ async function reinitPoolFromDbSettings() {
if (!res.rows.length) throw new Error('DB settings not found');
const dbSettings = res.rows[0];
// Закрываем старый пул
// Закрываем старый пул правильно
console.log('Закрываем старый пул подключений...');
await pool.end();
// Создаём новый пул с расшифрованными настройками
// Создаём новый пул с расшифрованными настройками и теми же параметрами для предотвращения утечек
pool = new Pool({
host: dbSettings.db_host_encrypted ? await decryptValue(dbSettings.db_host_encrypted) : process.env.DB_HOST || 'postgres',
port: parseInt(dbSettings.db_port || process.env.DB_PORT || '5432'),
@@ -80,6 +110,22 @@ async function reinitPoolFromDbSettings() {
user: dbSettings.db_user_encrypted ? await decryptValue(dbSettings.db_user_encrypted) : process.env.DB_USER || 'dapp_user',
password: dbSettings.db_password_encrypted ? await decryptValue(dbSettings.db_password_encrypted) : process.env.DB_PASSWORD,
ssl: false,
// Те же настройки для предотвращения утечек
max: 10,
min: 0,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
maxUses: 7500,
allowExitOnIdle: true,
});
// Устанавливаем лимит обработчиков для нового пула
pool.setMaxListeners(20);
// Добавляем обработчики ошибок для нового пула
pool.on('error', (err) => {
console.error('Unexpected error on idle client', err);
process.exit(-1);
});
// Пересоздаём session middleware

99
backend/routes/system.js Normal file
View File

@@ -0,0 +1,99 @@
/**
* Copyright (c) 2024-2025 Тарабанов Александр Викторович
* All rights reserved.
*
* This software is proprietary and confidential.
* Unauthorized copying, modification, or distribution is prohibited.
*
* For licensing inquiries: info@hb3-accelerator.com
* Website: https://hb3-accelerator.com
* GitHub: https://github.com/HB3-ACCELERATOR
*/
const express = require('express');
const router = express.Router();
const memoryMonitor = require('../utils/memoryMonitor');
const logger = require('../utils/logger');
const { checkAdminRole } = require('../services/admin-role');
// Middleware для проверки прав администратора
const requireAdmin = async (req, res, next) => {
try {
if (!req.session || !req.session.userId) {
return res.status(401).json({ success: false, error: 'Unauthorized' });
}
const isAdmin = await checkAdminRole(req.session.userId);
if (!isAdmin) {
return res.status(403).json({ success: false, error: 'Admin access required' });
}
next();
} catch (error) {
logger.error('Error checking admin role:', error);
res.status(500).json({ success: false, error: 'Internal server error' });
}
};
// GET /api/system/memory - Получить информацию о памяти
router.get('/memory', requireAdmin, (req, res) => {
try {
const memoryUsage = memoryMonitor.getMemoryUsage();
res.json({
success: true,
data: {
memory: memoryUsage,
timestamp: new Date().toISOString()
}
});
} catch (error) {
logger.error('Error getting memory usage:', error);
res.status(500).json({ success: false, error: 'Failed to get memory usage' });
}
});
// POST /api/system/memory/start - Запустить мониторинг памяти
router.post('/memory/start', requireAdmin, (req, res) => {
try {
const { interval } = req.body;
memoryMonitor.start(interval || 60000);
res.json({ success: true, message: 'Memory monitoring started' });
} catch (error) {
logger.error('Error starting memory monitoring:', error);
res.status(500).json({ success: false, error: 'Failed to start memory monitoring' });
}
});
// POST /api/system/memory/stop - Остановить мониторинг памяти
router.post('/memory/stop', requireAdmin, (req, res) => {
try {
memoryMonitor.stop();
res.json({ success: true, message: 'Memory monitoring stopped' });
} catch (error) {
logger.error('Error stopping memory monitoring:', error);
res.status(500).json({ success: false, error: 'Failed to stop memory monitoring' });
}
});
// GET /api/system/health - Проверка здоровья системы
router.get('/health', (req, res) => {
try {
const memoryUsage = memoryMonitor.getMemoryUsage();
const uptime = process.uptime();
res.json({
success: true,
data: {
status: 'healthy',
uptime: Math.round(uptime),
memory: memoryUsage,
timestamp: new Date().toISOString()
}
});
} catch (error) {
logger.error('Error getting system health:', error);
res.status(500).json({ success: false, error: 'Failed to get system health' });
}
});
module.exports = router;

View File

@@ -19,6 +19,7 @@ const { getBot } = require('./services/telegramBot');
const EmailBotService = require('./services/emailBot');
const { initDbPool, seedAIAssistantSettings } = require('./db');
const { warmupModel } = require('./scripts/warmup-model'); // Добавляем импорт разогрева модели
const memoryMonitor = require('./utils/memoryMonitor');
const PORT = process.env.PORT || 8000;
@@ -98,4 +99,25 @@ process.on('uncaughtException', (err) => {
logger.error('Uncaught Exception:', err);
});
// Запускаем мониторинг памяти в production
if (process.env.NODE_ENV === 'production') {
memoryMonitor.start(300000); // Каждые 5 минут
logger.info('[Server] Мониторинг памяти запущен в production режиме');
}
// Обработчики для корректного завершения
process.on('SIGINT', async () => {
logger.info('[Server] Получен сигнал SIGINT, завершаем работу...');
memoryMonitor.stop();
await initDbPool().then(pool => pool.end()); // Use initDbPool to get the pool
process.exit(0);
});
process.on('SIGTERM', async () => {
logger.info('[Server] Получен сигнал SIGTERM, завершаем работу...');
memoryMonitor.stop();
await initDbPool().then(pool => pool.end()); // Use initDbPool to get the pool
process.exit(0);
});
module.exports = app;

View File

@@ -29,6 +29,32 @@ const { isUserBlocked } = require('../utils/userUtils');
class EmailBotService {
constructor() {
// console.log('[EmailBot] constructor called');
this.imap = null;
this.isChecking = false;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 3;
}
// Метод для очистки IMAP соединения
cleanupImapConnection() {
if (this.imap) {
try {
// Удаляем все обработчики событий
this.imap.removeAllListeners('error');
this.imap.removeAllListeners('ready');
this.imap.removeAllListeners('end');
this.imap.removeAllListeners('close');
// Закрываем соединение
if (this.imap.state !== 'disconnected') {
this.imap.end();
}
} catch (error) {
logger.error('[EmailBot] Error cleaning up IMAP connection:', error);
} finally {
this.imap = null;
}
}
}
async getSettingsFromDb() {
@@ -533,56 +559,50 @@ class EmailBotService {
try {
// console.log('[EmailBot] start() called');
logger.info('[EmailBot] start() called');
const imapConfig = await this.getImapConfig();
// Логируем IMAP-конфиг (без пароля)
const safeConfig = { ...imapConfig };
if (safeConfig.password) safeConfig.password = '***';
logger.info('[EmailBot] IMAP config:', safeConfig);
// Очищаем предыдущее соединение если есть
this.cleanupImapConnection();
let attempt = 0;
const maxAttempts = 3;
this.isChecking = false;
const tryConnect = () => {
const tryConnect = async () => {
attempt++;
logger.info(`[EmailBot] IMAP connect attempt ${attempt}`);
this.imap = new Imap(imapConfig);
this.imap = new Imap(await this.getImapConfig());
// Устанавливаем обработчики событий
this.imap.once('ready', () => {
logger.info('[EmailBot] IMAP connection ready');
this.imap.openBox('INBOX', false, (err, box) => {
if (err) {
logger.error(`[EmailBot] Error opening INBOX: ${err.message}`);
this.imap.end();
return;
}
logger.info('[EmailBot] INBOX opened successfully');
});
// После успешного подключения — обычная логика
this.reconnectAttempts = 0; // Сбрасываем счетчик при успешном подключении
this.checkEmails();
logger.info('[EmailBot] Email bot started and IMAP connection initiated');
// Периодическая проверка почты
setInterval(async () => {
if (this.isChecking) return;
this.isChecking = true;
try {
await this.checkEmails();
} catch (e) {
logger.error('[EmailBot] Error in periodic checkEmails:', e);
}
this.isChecking = false;
}, 60000); // 60 секунд
});
this.imap.once('end', () => {
logger.info('[EmailBot] IMAP connection ended');
this.cleanupImapConnection();
});
this.imap.once('close', () => {
logger.info('[EmailBot] IMAP connection closed');
this.cleanupImapConnection();
});
this.imap.once('error', (err) => {
logger.error(`[EmailBot] IMAP connection error: ${err.message}`);
this.cleanupImapConnection();
if (err.message && err.message.toLowerCase().includes('timed out') && attempt < maxAttempts) {
logger.warn(`[EmailBot] IMAP reconnecting in 10 seconds (attempt ${attempt + 1})...`);
setTimeout(tryConnect, 10000);
}
});
this.imap.connect();
};
tryConnect();
} catch (err) {
// console.error('[EmailBot] Ошибка при старте:', err);
logger.error('[EmailBot] Ошибка при старте:', err);
this.cleanupImapConnection();
throw err;
}
}

View File

@@ -0,0 +1,99 @@
/**
* Copyright (c) 2024-2025 Тарабанов Александр Викторович
* All rights reserved.
*
* This software is proprietary and confidential.
* Unauthorized copying, modification, or distribution is prohibited.
*
* For licensing inquiries: info@hb3-accelerator.com
* Website: https://hb3-accelerator.com
* GitHub: https://github.com/HB3-ACCELERATOR
*/
const logger = require('./logger');
class MemoryMonitor {
constructor() {
this.monitoring = false;
this.interval = null;
this.lastMemoryUsage = null;
}
start(intervalMs = 60000) { // По умолчанию каждую минуту
if (this.monitoring) {
logger.warn('[MemoryMonitor] Мониторинг уже запущен');
return;
}
this.monitoring = true;
this.interval = setInterval(() => {
this.checkMemoryUsage();
}, intervalMs);
logger.info('[MemoryMonitor] Мониторинг памяти запущен');
}
stop() {
if (this.interval) {
clearInterval(this.interval);
this.interval = null;
}
this.monitoring = false;
logger.info('[MemoryMonitor] Мониторинг памяти остановлен');
}
checkMemoryUsage() {
const memUsage = process.memoryUsage();
const memUsageMB = {
rss: Math.round(memUsage.rss / 1024 / 1024),
heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024),
heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024),
external: Math.round(memUsage.external / 1024 / 1024),
arrayBuffers: Math.round(memUsage.arrayBuffers / 1024 / 1024)
};
// Проверяем рост памяти
if (this.lastMemoryUsage) {
const growth = {
rss: memUsageMB.rss - this.lastMemoryUsage.rss,
heapUsed: memUsageMB.heapUsed - this.lastMemoryUsage.heapUsed
};
// Логируем если есть значительный рост
if (growth.rss > 50 || growth.heapUsed > 20) {
logger.warn('[MemoryMonitor] Обнаружен рост памяти:', {
current: memUsageMB,
growth: growth
});
}
}
this.lastMemoryUsage = memUsageMB;
// Логируем текущее использование памяти
logger.info('[MemoryMonitor] Использование памяти:', memUsageMB);
}
getMemoryUsage() {
const memUsage = process.memoryUsage();
return {
rss: Math.round(memUsage.rss / 1024 / 1024),
heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024),
heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024),
external: Math.round(memUsage.external / 1024 / 1024),
arrayBuffers: Math.round(memUsage.arrayBuffers / 1024 / 1024)
};
}
// Проверка утечек в EventEmitter
checkEventEmitterLeaks() {
const eventEmitter = require('events');
const defaultMaxListeners = eventEmitter.defaultMaxListeners;
logger.info('[MemoryMonitor] EventEmitter defaultMaxListeners:', defaultMaxListeners);
// Можно добавить дополнительную логику для проверки конкретных EventEmitter'ов
}
}
module.exports = new MemoryMonitor();