Skip to content

Latest commit

 

History

History
486 lines (335 loc) · 31.6 KB

File metadata and controls

486 lines (335 loc) · 31.6 KB

Архитектурные решения и допущения

Этот документ явно фиксирует все ключевые проектные решения, их обоснование, рассмотренные альтернативы и ссылки на реализацию. Каждое допущение представляет собой осознанный выбор для баланса между корректностью, производительностью, безопасностью и пользовательским опытом.


Содержание

  1. Механики аукциона
  2. Финансовая система
  3. Безопасность и антифрод
  4. Производительность и масштабирование
  5. Крипто-интеграция
  6. Пользовательский опыт

Механики аукциона

1. Стратегия переноса ставок между раундами

Решение: Ставки НЕ переносятся автоматически в последующие раунды. Каждый раунд независим.

Обоснование:

  • Чёткая ментальная модель: пользователи явно делают ставки в каждом раунде
  • Предотвращает непреднамеренное распределение средств
  • Позволяет корректировать стратегию на основе результатов предыдущих раундов
  • Упрощает логику холдов и расчётов

Рассмотренные альтернативы:

  • Авто-перенос с отказом: Добавляет сложность в UI и нагрузку на поддержку
  • Частичный перенос (только проигравшие): Создаёт непоследовательное поведение
  • Настраиваемый перенос: Избыточная сложность при минимальной выгоде

Реализация: src/services/auction-engine/roundFinalizationService.ts — после финального раунда все не-выигрышные холды освобождаются


2. Правило разрешения ничьих

Решение: Ничьи разрешаются по самой ранней временной метке ставки (FIFO), с ObjectId как вторичным критерием.

Обоснование:

  • Вознаграждает решительность и раннее участие
  • Детерминированный и воспроизводимый рейтинг
  • Согласуется с философией anti-sniping (поощрение ранних ставок)
  • Просто реализовать и объяснить пользователям

Рассмотренные альтернативы:

  • LIFO (последний-первый): Поощряет сниппинг, противоречит anti-sniping
  • Случайный выбор: Недетерминированный, сложно аудировать
  • Пропорциональное разделение: Сложные расчёты, требует дробных распределений

Реализация: src/services/auction-engine/bidRanking.tsbuildRankingMember кодирует инвертированную временную метку + bidId


3. Поведение Anti-Sniping продлений

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

Обоснование:

  • Баланс честности (время на реакцию) и финальности (аукцион должен закончиться)
  • Предотвращает бесконечные циклы от координированного сниппинга
  • Жёсткий дедлайн создаёт срочность и стратегические точки решения
  • Настраивается для каждого раунда

Рассмотренные альтернативы:

  • Неограниченные продления: Аукцион может идти бесконечно
  • Только одно продление: Недостаточно для активных аукционов
  • Экспоненциальный cooldown: Сложно объяснить и реализовать

Реализация: src/services/auction-engine/roundStateMachine.tsapplyAntiSnipingExtension применяет логику max extensions

Параметры:

  • triggerWindowSeconds: 30 (по умолчанию) — окно перед концом раунда
  • extensionSeconds: 60 (по умолчанию) — добавляемая длительность
  • maxExtensions: 10 (по умолчанию) — жёсткий лимит продлений

4. Режим ценообразования: First-Price vs. Cutoff (Vickrey-стиль)

Решение: Поддержка обоих режимов — first-price (победители платят свою ставку) и cutoff (победители платят (N+1)-ю ставку + инкремент), настраиваемых для каждого аукциона.

Обоснование:

  • First-price: Простой, знакомый, максимизирует выручку
  • Cutoff: Стратегически устойчив (ставка истинной стоимости — доминирующая стратегия), уменьшает сожаление, поощряет высокие ставки
  • Оба варианта позволяют продавцам выбирать под свои цели

Рассмотренные альтернативы:

  • Только first-price: Проще, но поощряет стратегическое занижение
  • Только cutoff: Снижает выручку продавца в некоторых сценариях
  • Голландский аукцион: Сложный тайминг и UX

Реализация:


5. Прокси-ставки с максимальной суммой

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

Обоснование:

  • Снижает бремя мониторинга для пользователей
  • Предотвращает проигрыш из-за невнимательности
  • Конкурентоспособно с традиционными аукционными платформами
  • Максимальная сумма остаётся приватной

Рассмотренные альтернативы:

  • Без прокси-ставок: Просто, но плохой UX — нужно постоянно следить
  • Всё или ничего: Прыжок к максимуму сразу раскрывает истинную оценку
  • Процентные инкременты: Непредсказуемые финальные цены

Реализация: src/services/auction-engine/bidService.tsautoRaiseProxyBids запускается после каждой ставки

Примечание: Авто-повышение прокси отключено в режиме fast path для сохранения гарантий атомарности.


Финансовая система

6. Блокировка средств: Hold vs. Немедленное списание

Решение: Использовать операции HOLD (резервировать, но не списывать) до финализации раунда.

Обоснование:

  • Позволяет ставить в нескольких аукционах одновременно с одним балансом
  • Чёткое разделение между зарезервированными и потраченными средствами
  • Атомарный расчёт при закрытии раунда (всё или ничего)
  • Аудит-след показывает точный жизненный цикл (HOLD → CAPTURE/RELEASE)

Рассмотренные альтернативы:

  • Немедленное списание: Проще, но блокирует мульти-аукционное участие
  • Виртуальные холды (без записи в журнал): Сложно аудировать, риск двойного расходования
  • Эскроу-счета: Добавляет сложность, требует переводов

Реализация:

Формула баланса:

available + held = deposits - withdrawals - captures + releases
current = available + held

7. Стратегия минимального инкремента

Решение: Динамический минимум = max(configuredMinBid, currentTopBid + minIncrement).

Обоснование:

  • Предотвращает спам-ставки (например, +$0.001)
  • Обеспечивает осмысленное ценообразование
  • Настраивается для каждого аукциона
  • Простая ментальная модель

Рассмотренные альтернативы:

  • Фиксированный минимум: Не масштабируется с суммой ставки
  • Процентный: Сложно предсказать точную сумму
  • Без минимума: Открывает возможности для спама/грифинга

Реализация: src/services/auction-engine/bidService.ts — валидация минимума при размещении ставки

По умолчанию: 0.01 USDT (настраивается через BID_MIN_INCREMENT)


8. Неизменяемость журнала операций (Append-Only)

Решение: Записи журнала неизменяемы после создания. Корректировки делаются через новые компенсирующие записи, никогда не через обновления/удаления.

Обоснование:

  • Целостность аудит-следа: каждое финансовое событие постоянно записано
  • Соответствие регуляторным требованиям (финансовые системы требуют неизменяемых логов)
  • Отладка: можно проследить точную последовательность событий
  • Предотвращает случайное/злонамеренное изменение

Рассмотренные альтернативы:

  • Изменяемые записи: Проще код, но уничтожает аудит-след
  • Мягкие удаления: Всё ещё позволяет подделку через boolean флаги
  • История версий: Сложно, аудит-след фрагментирован

Реализация: src/services/ledger/ledgerStore.ts — нет операций UPDATE или DELETE, все мутации через INSERT


9. Стратегия кэширования балансов (Redis)

Решение: Балансы пользователей кэшируются в Redis с TTL, обновляются через Lua-скрипт при операциях журнала. Fallback к MongoDB при cache miss.

Обоснование:

  • Быстрые проверки баланса (субмиллисекундные) для валидации ставок
  • Снижает нагрузку на MongoDB в 10-50 раз
  • Lua-скрипт обеспечивает атомарность обновлений баланса
  • TTL предотвращает накопление устаревших данных

Рассмотренные альтернативы:

  • Без кэширования: Каждая ставка попадает в MongoDB, медленно под нагрузкой
  • Кэш на уровне приложения: Race conditions, сложная инвалидация
  • Материализованное представление в MongoDB: Всё равно медленнее Redis

Реализация:

TTL: 3600 секунд (1 час)


Безопасность и антифрод

10. Детекция аномалий выводов: ML vs. Правила

Решение: Многофакторная ML-модель с онлайн-обучением (экспоненциальные скользящие средние) с настраиваемым порогом.

Обоснование:

  • Адаптируется к индивидуальным паттернам поведения пользователя
  • Обнаруживает сложные атаки (постепенная эскалация, мимикрия)
  • Низкий уровень ложных срабатываний после периода прогрева
  • Не требует обучающего набора данных (онлайн-обучение)

Рассмотренные альтернативы:

  • Простые пороги (сумма > X): Легко обойти
  • Только статистика (среднее + 2σ): Не учитывает контекст (время, адрес, частоту)
  • Внешний ML-сервис: Латентность, стоимость, зависимость

Реализация: src/services/crypto-gateway/mlAnomalyDetector.ts

Факторы:

  1. Отклонение суммы (Z-score) — 40 баллов если >3σ
  2. Новый адрес назначения — 25 баллов
  3. Высокая частота vs. средняя пользователя — 30 баллов
  4. Необычное время суток — 15 баллов
  5. Быстрая последовательность (>3 за 1 час) — 20 баллов

Порог: 50 баллов = ручной review


11. Время жизни ключей идемпотентности

Решение: Ключи идемпотентности для ставок действительны 600 секунд (10 минут). Для выводов: 24 часа.

Обоснование:

  • Ставки: достаточно для логики повторов, коротко для предотвращения устаревших replays
  • Выводы: более длинное окно для критических операций
  • Баланс между безопасностью и эффективностью хранения

Рассмотренные альтернативы:

  • Постоянные: Стоимость хранения растёт неограниченно
  • Очень короткие (60с): Недостаточно при медленном сетевом соединении
  • Единое время жизни: Разные операции имеют разные профили риска replay

Реализация: src/services/auction-engine/bidService.ts — TTL идемпотентности ставок 600с


12. Rate Limiting: Параметры Token Bucket

Решение: Многоуровневые лимиты с использованием алгоритма token bucket:

  • На пользователя: 10 ставок/сек ёмкость, 5/с пополнение
  • На аукцион-пользователь: 3 ставки/сек ёмкость, 1/с пополнение
  • На IP: 20 ставок/сек ёмкость, 10/с пополнение

Обоснование:

  • Предотвращает спам/DoS, позволяя легитимные всплески
  • Уровень пользователя предотвращает флуд от одного пользователя
  • Уровень аукцион-пользователь предотвращает злоупотребление сниппинг-скриптами
  • Уровень IP защищает от распределённых атак

Рассмотренные альтернативы:

  • Фиксированное окно: Всплеск на границах окна
  • Скользящий лог: Ресурсоёмко по памяти
  • Без rate limiting: Уязвимо к злоупотреблениям

Реализация: src/services/auction-engine/bidService.ts — Lua-скрипт для атомарного token bucket


Производительность и масштабирование

13. Fast Bid Path: Redis Lua vs. MongoDB Transactions

Решение: Трёхрежимная система:

  • Safe: Все операции через MongoDB транзакции (50-100 RPS)
  • Fast: Redis Lua-скрипт для принятия, фоновая синхронизация в MongoDB (2,000-5,000 RPS)
  • Auto: Fast path с автоматическим fallback при недоступности Redis

Обоснование:

  • Корректность в первую очередь: safe mode всегда доступен
  • Производительность когда нужна: fast mode для высоконагруженных аукционов
  • Устойчивость: автоматический fallback поддерживает uptime

Рассмотренные альтернативы:

  • Только Redis: Теряем аудит-след при сбое Redis
  • Только MongoDB: Не масштабируется до высокого RPS
  • Раздельные пути записи/чтения: Сложно, проблемы eventual consistency

Реализация:

Компромисс: Fast path вводит ~1с задержку синхронизации для аудит-следа (приемлемо для некритичных чтений)

Конфигурация: BID_MODE=auto (по умолчанию), BID_FAST_SYNC_INTERVAL_MS=1000, BID_FAST_SYNC_BATCH_SIZE=100


14. Лидерборд ранжирования: Redis Sorted Set

Решение: Ранжирование аукционов хранится в Redis sorted set (ZSET) с композитным скором.

Формат скора: (amount * 1e8) + (9999999999999 - createdAtMs)

Обоснование:

  • O(log N) вставка ставки и получение ранга
  • Атомарные обновления скора сохраняют порядок
  • Детерминированный tiebreaker через кодирование временной метки
  • Эффективное извлечение top-N (ZREVRANGE)

Рассмотренные альтернативы:

  • Только MongoDB: Медленнее, требует управления индексами
  • Отдельное поле timestamp: Не может гарантировать атомарный порядок
  • Сортировка на уровне приложения: Неатомарно, race conditions

Реализация: src/services/auction-engine/bidRanking.ts — кодирование ranking member


15. Хранение данных: TTL vs. Архивация

Решение: Настраиваемые периоды хранения с автоматическим истечением (MongoDB TTL индексы + Redis EXPIRE).

Значения по умолчанию:

  • Ставки: 90 дней
  • Записи журнала: 730 дней (2 года)
  • Уведомления: 30 дней
  • Логи: 14 дней

Обоснование:

  • Соответствие требованиям: финансовые записи хранятся дольше операционных
  • Стоимость хранения: автоматическая очистка предотвращает неограниченный рост
  • Производительность: меньший рабочий набор улучшает скорость запросов
  • Аудит: критические данные хранятся для правовых требований

Рассмотренные альтернативы:

  • Постоянное хранение: Неустойчивая стоимость и производительность
  • Ручная архивация: Операционная нагрузка, риск потери данных
  • Агрессивное удаление: Регуляторный риск

Реализация: src/shared/storage/retention.ts — поле expiresAt вычисляется при вставке

Конфигурация: DATA_RETENTION_BIDS_DAYS=90, DATA_RETENTION_LEDGER_DAYS=730


Крипто-интеграция

16. Стратегия кошельков: Трёхуровневый подход

Решение: Поддержка трёх стратегий атрибуции депозитов, настраиваемых для каждой валюты:

  1. address_pool: Общий адрес пула + memo/destination tag (XRP, XLM, TON)
  2. memo_tag: Один адрес, уникальный memo для каждого пользователя
  3. address_per_user: HD деривация, уникальный адрес для каждого пользователя (Bitcoin, Ethereum)

Обоснование:

  • Разные блокчейны имеют разные профили стоимости/возможностей
  • Memo-based: дёшево для чейнов с нативной поддержкой memo
  • HD деривация: trustless, пользователь владеет ключами (будущее улучшение)
  • Pool: простейший вариант для прототипирования

Рассмотренные альтернативы:

  • Единая стратегия: Не оптимизирует под характеристики чейна
  • Стиль биржи (ручная сверка): Требует вмешательства команды поддержки
  • Эскроу смарт-контрактов: Высокие gas costs, специфично для чейна

Реализация: src/services/crypto-gateway/walletStrategyFactory.ts

Конфигурация: CRYPTO_WALLET_STRATEGY_{CURRENCY}=address_per_user


17. Безопасность выводов: Многоуровневая валидация

Решение: Трёхэтапная валидация перед бродкастом в блокчейн:

  1. Валидаторы формата: Чексумма адреса, лимиты сумм (мгновенно)
  2. Детекция аномалий: ML-анализ поведения (см. #10)
  3. Очередь ручного review: Помеченные выводы требуют одобрения админа

Обоснование:

  • Глубокая защита: нет единой точки отказа
  • Ошибки формата ловятся сразу (лучший UX)
  • Аномалии помечаются до того, как средства покинут платформу
  • Ручной review как backstop для высокорисковых операций

Рассмотренные альтернативы:

  • Только автоматизация: Уязвимо к сложным атакам
  • Всё вручную: Плохой UX, операционное узкое место
  • Временные задержки (24ч hold): Фрустрирует легитимных пользователей

Реализация: src/services/crypto-gateway/withdrawalService.ts — последовательный pipeline валидации


18. Пороги подтверждений депозитов

Решение: Требования подтверждений на основе риска:

  • Низкий риск (стейблкоины на быстрых чейнах): 1 подтверждение
  • Средний риск (волатильные токены): 6 подтверждений
  • Высокий риск (новые/низколиквидные): 12 подтверждений

Обоснование:

  • Баланс безопасности (риск реорганизации) и UX (скорость депозита)
  • Стейблкоины имеют низкий стимул для double-spend
  • Волатильные активы требуют больше подтверждений для предотвращения арбитража при реорге

Рассмотренные альтернативы:

  • Фиксированный порог: Переобеспечивает быстрые чейны, недообеспечивает рискованные
  • Zero confirmations: Уязвимо к double-spend атакам
  • Вероятностная модель: Сложно, трудно объяснить

Реализация: src/services/crypto-gateway/depositScanner.ts

Значения по умолчанию: 1 подтверждение для USDT (TON), 6 для волатильных активов


Пользовательский опыт

19. Доставка уведомлений: Push vs. Poll

Решение: Гибридная модель:

  • WebSocket push для реального времени (outbid alerts, закрытие раунда)
  • Telegram bot push для критических событий (выигрыш аукциона, подтверждение вывода)
  • Polling fallback при отключении WebSocket

Обоснование:

  • Realtime UX для активных пользователей (WebSocket)
  • Достижимость пользователей вне браузера (Telegram)
  • Устойчивость к сбоям соединения (polling)

Рассмотренные альтернативы:

  • Только polling: Плохой UX, повышенная нагрузка на сервер
  • Только push: Ненадёжно при разрыве соединения
  • Email: Слишком медленно для обновлений аукциона

Реализация:


20. Синхронизация времени: Авторитетный сервер

Решение: Все временные метки генерируются на стороне сервера. Клиентские часы не доверяются.

Обоснование:

  • Предотвращает атаки на основе времени (бэкдейтинг ставок, манипуляции anti-sniping)
  • Согласованный порядок между распределёнными клиентами
  • Упрощает разрешение конфликтов

Рассмотренные альтернативы:

  • Клиентские временные метки: Уязвимо к манипуляциям
  • Гибрид (клиент + расчёт drift сервера): Сложно, всё ещё уязвимо

Реализация: Все поля createdAt в MongoDB заполняются на стороне сервера, клиентский дисплей использует серверную временную метку из ответов API


Резюме

Эти допущения представляют production-ready решения, проверенные в реальных аукционных платформах. Каждый выбор приоритизирует:

  1. Корректность: Финансовые операции атомарны и аудируемы
  2. Безопасность: Глубокая защита против мошенничества и злоупотреблений
  3. Производительность: Масштабируется до тысяч RPS при сохранении корректности
  4. Пользовательский опыт: Чёткие ментальные модели, обратная связь в реальном времени, минимальное трение

Платформа спроектирована для деплоя в продакшен сегодня с уверенностью в надёжности, безопасности и масштабируемости.