github BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot v3.23.1

10 hours ago

3.23.1 (2026-03-06)

Масштабный релиз безопасности и стабильности: устранены race conditions в платёжной системе, завершена миграция FK-ограничений, исправлены ошибки при мерже аккаунтов и работе с промокодами.


🔒 Платёжная система — race conditions и атомарность

4984f20 — Комплексный аудит и исправление всех 10 платёжных провайдеров.

Проблема: При одновременных webhook-вызовах баланс мог зачислиться дважды — 8 из 10 провайдеров не имели защиты от гонок.

Что сделано:

  • SELECT FOR UPDATE блокировка строки платежа во всех 9 провайдерах (CryptoBot, Heleket, MulenPay, Pal24, Wata, Platega, CloudPayments, Freekassa, KassaAI). YooKassa уже имела свой паттерн
  • Атомарный коммит: create_transaction(commit=False) + единый db.commit() — баланс и транзакция фиксируются в одной DB-операции. Раньше create_transaction() коммитил сам, и при падении между коммитами транзакция создавалась, но баланс не обновлялся
  • emit_transaction_side_effects() — события (event_emitter, promo-группы, реферальные конкурсы) вызываются после коммита
  • Все link_*_payment_to_transaction используют db.flush() вместо db.commit()
  • verify_payment_amount() — утилита проверки суммы webhook vs ожидаемой, для защиты от манипуляций
  • Platega: блокировка перенесена перед чтением metadata, обновление полей инлайн (без CRUD-функции которая коммитит)
  • CloudPayments: int(round(amount * 100)) вместо int(amount * 100) — устранение ошибок округления float→kopeks
  • Heleket добавлен в SUPPORTED_AUTO_CHECK_METHODS
  • PII удалены из логов yookassa webhook (заголовки запроса, тело, IP-адреса)
  • UniqueConstraint(external_id, payment_method) на таблице transactions + Alembic миграция 0017 с дедупликацией
  • Cabinet: PaymentService(bot=bot) — исправлена инициализация без бота в admin_payments и balance

📦 31 файл изменён, +460 / −117 строк


🗃️ Миграция FK-ограничений

34c82c3 — ON DELETE CASCADE/SET NULL на все FK к users.id.

Проблема: 27 FK-ограничений к users.id не имели ondelete — при удалении юзера или восстановлении бэкапа с сиротами constraints нарушались.

Что сделано (миграция 0016):

  1. Очистка сирот во всех 53 child-таблицах (DELETE для non-nullable, SET NULL для nullable полей)
  2. Пересоздание 27 FK с ON DELETE CASCADE (для user_id) или SET NULL (для created_by, processed_by)

📦 2 файла, +166 / −27 строк


fe393d2 — Доработка миграции: добавлены 27 пропущенных constraints.

  • broadcast_history.admin_id: изменён с CASCADE на SET NULL (nullable поле, сохраняем аудит)
  • Добавлены 27 FK, которые были только очищены от сирот, но не пересозданы с ondelete
  • Итого все 53 FK→users.id теперь корректно обработаны

📦 2 файла, +31 / −3 строки


🔀 Мерж аккаунтов

1c89bd8 — UniqueViolation при мерже аккаунтов с общим OAuth/Telegram/Email ID.

Проблема: SQLAlchemy не гарантирует порядок UPDATE при flush() — если primary обновлялся раньше secondary, unique constraint срабатывал до очистки старого значения.

Решение: Очистка secondary → flush() → установка primary. Гарантирует что старое значение удалено до присвоения нового.

📦 1 файл, +25 / −9 строк


00a7db2 — Дедупликация promocode_uses при мерже аккаунтов.

Проблема: После добавления UniqueConstraint(user_id, promocode_id), простое переназначение user_id при мерже падало с IntegrityError если оба юзера использовали один промокод.

Решение: Перед переназначением удаляются дубликаты — записи secondary, для которых у primary уже есть использование того же промокода.

📦 1 файл, +8 / −1 строка


🎟️ Промокоды

7fb839a — Конвертация триалов, race condition, savepoints.

Проблемы:

  • Trial-подписки отказывались при активации промокода (~20 из 300 юзеров)
  • Race condition при параллельном использовании промокода
  • commit()/rollback() в create_promocode_use ломали DB-сессию при ошибках

Что сделано:

  • extend_subscription: добавлен переход TRIAL → ACTIVE — триальные подписки конвертируются в платные
  • UniqueConstraint на PromoCodeUse(user_id, promocode_id) + миграция 0015 с дедупликацией
  • create_promocode_use: begin_nested() + flush() вместо commit()/rollback() (без коррупции сессии)
  • Race condition: create_promocode_use вызывается ДО _apply_promocode_effects (резервирование)
  • Atomic SQL INCREMENT для current_uses (защита от lost-update)
  • mark_user_as_had_paid_subscription: savepoint вместо commit/rollback
  • Удалён мёртвый код: use_promocode(), trial_subscription_not_eligible

📦 9 файлов, +92 / −47 строк


👥 RBAC — системные роли

7a7fb71 — Дубликаты системных ролей при переименовании.

Проблема: Bootstrap искал роли по name — переименование через UI создавало дубликат при следующем запуске.

Решение: Поиск по (is_system, level) вместо name. Bootstrap только добавляет новые permissions, не перезатирая кастомизацию админа.

📦 1 файл, +31 / −8 строк


🏆 Реферальные конкурсы

6713b34 — Комплексное исправление системы конкурсов.

  • Float precision: int(round(amount * 100)) вместо int(amount * 100) для рубли→копейки
  • Порядок callback-хендлеров: специфичные startswith регистрируются первыми
  • FSM state filter на callback для предотвращения случайных срабатываний
  • Upsert в add_contest_event вместо дубликатов
  • SQL фильтр в get_contests_for_events — все активные конкурсы, не только по дате
  • Нормализация end-of-day (23:59:59.999999) для границ конкурсных периодов
  • Guard is_completed в create_transaction

📦 6 файлов, +42 / −11 строк


🖥️ UI — Админ-панель

04562fd — Кнопка «Назад» в тарифах вела в настройки вместо админ-панели.

Проблема: Тарифы доступны напрямую из главного меню админки, но callback_data кнопки «Назад» указывал на admin_submenu_settings.

Решение: Заменён на admin_panel во всех 4 местах.

📦 1 файл, 4 строки изменены

Don't miss a new remnawave-bedolaga-telegram-bot release

NewReleases is sending notifications on new releases.