Эксплуатация и безопасность
Эксплуатация и безопасность — как биржа не падает и не теряет чужие деньги
Представьте вечер пятницы. Первый алерт: задержка matching engine на одной из торговых пар выросла с обычных 200 микросекунд до 4 миллисекунд. Через секунду приходит второй: спред на той же паре расширился с 2 базисных пунктов до 40, маркет-мейкеры снимают заявки. Третий алерт объясняет первые два: на основном хосте из зеркального RAID-массива выпал один из NVMe-дисков, куда пишется журнал событий. Теперь каждый fsync() идёт через единственный оставшийся диск и занимает три миллисекунды вместо привычных восьмидесяти микросекунд. Пока fsync() не завершён, ордер не считается применённым. Основной узел жив, торги продолжаются, но каждый ордер упирается в диск. У вас есть несколько минут, чтобы решить: сразу переключиться на резервный узел или работать в деградированном режиме, пока инженеры заменяют NVMe.
Это — реальная жизнь CEX. Все восемь предыдущих разделов рассказывали о том, как устроена биржа. Этот раздел — о том, как она продолжает работать, когда что-то идёт не по плану. А идёт не по плану всегда. Вопрос только — как часто, насколько публично и сколько это стоит клиентам.
Высокая доступность: живая биржа — это всегда две биржи
Биржа, работающая на одной машине, — это не биржа. Это демо. Как только первый реальный клиент доверил ей свои деньги, у вас появляется SLA: не ронять.
Мы уже видели в разделе про matching engine, что вся горячая часть биржи — matching, book state, order sequencing — работает в одном процессе, и это правильно. Но «один процесс» не означает «одна машина». Для высокой доступности биржа держит как минимум две копии matching engine: основную и резервную.
Как их синхронизировать? Ответ, как почти везде в этом гайде, — через журнал событий.
- — обрабатывает поток ордеров
- — пишет каждое событие в журнал
- — эмитит trade events наружу
seq 1002 → FILL
seq 1003 → CANCEL
seq 1004 → FILL
...
- — читает журнал по порядку
- — прогоняет те же операции
- — держит идентичное состояние
Основной узел принимает все ордера и для каждого события — нового ордера, отмены, филла — создаёт детерминированную запись в append-only журнал: seq=..., type=..., payload=.... Только после fsync() журнала событие считается применённым. Резервный узел читает тот же журнал, прогоняет те же операции в той же последовательности и держит у себя идентичное состояние книги.
Ключевое слово здесь — детерминизм. Если основной узел на ордере seq=1002 взял блокировку на три микросекунды, а резервный — на семь, это нормально. Но если они расходятся в результате матчинга — скажем, основной дал событиям матчинга порядок A→B, а резервный B→A — всё, состояние разъезжается, и резервный узел превращается в тыкву. Чтобы этого не случилось, matching engine должен быть чистой функцией от журнала: на одном и том же входе — один и тот же выход, без обращения к системному времени, без генерации случайных чисел, без зависимости от порядка сетевых пакетов.
Это — тот самый event sourcing из архитектуры LMAX. Биржа хранит не текущее состояние книги, а последовательность событий, из которой состояние восстанавливается детерминированным проигрыванием журнала. При падении основного узла оркестратор переключает входящий трафик на резервный, который уже имеет актуальное состояние. Потеря — в худшем случае несколько ордеров, которые ещё не успели примениться: их биржа обязана отклонить с понятным кодом ошибки, чтобы клиент мог повторить.
Почему активная/пассивная пара, а не две активные? Потому что две matching engine, одновременно принимающие ордера на одну и ту же пару, — это рассинхронизация, запрограммированная архитектурно. Если обе инстанции получают ордера параллельно, то с точки зрения одной копии первым приходит один ордер, а с точки зрения другой — другой. Согласовать два таких «первым пришёл» можно только через консенсус. А консенсус — это миллисекунды там, где биржа считает время в микросекундах.
Для леджера и market data fan-out ситуация проще: эти подсистемы читают журнал и могут реплицироваться параллельно без ограничений. Matching — только одно место, где ордера выстраиваются в очередь. Именно поэтому архитектура биржи — асимметричная: hot path (matching) живёт в активной/пассивной паре, всё остальное масштабируется горизонтально.
Несколько регионов. Если целый регион AWS становится недоступен, включается межрегиональный failover. В другом регионе работает «тёплый» резерв, который также читает журнал, но получает его с задержкой в несколько десятков миллисекунд. Переключение занимает минуты, поэтому такой резерв используют только при катастрофическом отказе. За него приходится платить хранением дополнительной копии данных и межрегиональным трафиком, но для серьёзной биржи зависимость от одного региона неприемлема.
Запас мощности: как готовиться к десятикратным всплескам
Биржа должна держать нормальную нагрузку в штатном режиме. Это — базовый минимум. Настоящий вопрос — что происходит, когда нагрузка внезапно становится в десять раз больше обычной.
Такие моменты случаются по двум причинам:
- Листинг популярной монеты. Биржа объявляет в твиттере: «через 10 минут открываем торги новой монеты». У скрипт-ботов запускается обратный отсчёт, розничные трейдеры бросаются перестраивать балансы, маркет-мейкеры прогревают соединения. В момент открытия торгов нагрузка — и на публичный API, и на matching engine — подскакивает в разы.
- Обвал рынка. Bitcoin летит вниз на 8% за час. Все хотят или выйти, или купить. Поток ордеров, поток отмен, поток WebSocket-подписок — всё растёт одновременно.
Правильный ответ на такие события — заранее заложенный headroom. В обычный день биржа работает на 30–40% реальной ёмкости, чтобы во время пика подняться до 90%, а затем вернуться к нормальной нагрузке. Этот запас напрямую отражается в счёте за инфраструктуру: большую часть времени дополнительные экземпляры почти простаивают, зато в критические минуты принимают на себя всплеск трафика.
Есть ещё один источник сложности: подсистемы масштабируются по-разному. Gateway и market data fan-out — почти линейно: добавили ресурсы и обработали больше запросов. Matching engine для одной пары не масштабируется горизонтально: у него одна очередь ордеров и один активный экземпляр. Увеличить общую пропускную способность можно шардированием по символам (per-symbol sharding из третьего раздела), а ускорить отдельный символ — более мощной машиной. Для листинга популярной монеты ей часто заранее выделяют отдельный физический узел.
Что биржа делает перед анонсом, который она контролирует (листинг):
- Прогревает пулы соединений. Gateway заранее поднимает воркеры, WebSocket сервера открывают сокеты заранее.
- Изолирует новую пару. Поднимает отдельный matching engine instance, чтобы всплеск на BTC/NEW не затронул BTC/USDT.
- Отключает дорогие фичи. Временно отключает сложные отчёты, медленные административные выгрузки, всё, что не критично для торговли.
- Увеличивает лимиты на ряд подсистем и уменьшает лимиты на некоторые API, чтобы скрипт-боты не смогли сожрать весь rate limit.
- Собирает on-call-команду. Старшие SRE и инженеры matching engine вместе следят за дашбордами и готовы вмешаться.
То, что не контролируется (обвал рынка), — это проверка на то, правильно ли биржа заложила headroom за последние 12 месяцев. Биржа, которая падает под таким событием, рискует не только деньгами сегодня, но и клиентами навсегда: «она легла, когда мне надо было выйти» — это самое неприятное воспоминание, которое остаётся у пользователя о любой бирже.
Безопасность: defense in depth
Слова «безопасность CEX» обычно вызывают в голове одну ассоциацию — кошельки. Это правильно, но слишком узко. Настоящая модель безопасности биржи — это defense in depth: много слоёв, каждый защищает следующий, и атакующий должен пробить все, чтобы добраться до денег.
Разберём слои изнутри наружу.
1. Холодное хранение. Основная масса клиентских активов — в кошельках, которые физически отключены от сети. Их ключи не хранятся на серверах биржи вообще; они лежат в сейфе в отдельной юрисдикции, и для подписания транзакции требуется ручная процедура с m-из-n подписантами. Это самый последний барьер: даже если взломали всё остальное, из холодного кошелька украсть нельзя без того, чтобы атакующий оказался физически у сейфа с нужным числом подписантов.
2. HSM или MPC для «рабочих» ключей. Для hot и warm кошельков, из которых биржа ежедневно платит выводы, приватные ключи не лежат в файле на сервере. Они живут либо в hardware security module (HSM) — выделенном устройстве, которое физически не отдаёт ключ наружу, только подпись, — либо в multi-party computation (MPC) схеме, где «ключ» разделён между несколькими серверами и никто в одиночку не может его восстановить. Это защита от компрометации одного сервера: взломали машину — ключа там всё равно нет.
3. Авторизация. Для каждого API-запроса система проверяет, разрешена ли пользователю нужная операция и не превышены ли лимиты. Здесь работают роли, scopes, права API-ключей и rate limits. Типичная ошибка уровня security 101 — забыть об авторизации на редко используемом административном endpoint, который затем открывает доступ к чужому аккаунту.
4. Аутентификация. Прежде чем что-то авторизовывать, нужно знать, кто пришёл. Для клиентского UI — OAuth, WebAuthn, обязательный 2FA. Для API — ключ плюс HMAC-подпись с timestamp (мы видели этот паттерн в разделе 8). Для сотрудников биржи — SSO, обязательные hardware-токены, короткие сессии. Пароли — только как один фактор из многих, и никогда единственный.
5. Периметр сети. Всё, что выше, защищается внешним слоем: WAF от типичных атак, DDoS-защита от объёмных атак, сегментация VPC — чтобы даже если атакующий пробился через frontend, он не смог дойти до matching engine по внутренней сети. Администраторские интерфейсы — вообще в отдельной сети, доступной только через VPN с hardware-токеном.
Главное в defense in depth — асимметрия в пользу защитника: чтобы добраться до денег, атакующему нужно пробить все слои подряд; защитнику достаточно, чтобы хотя бы один из них удержал. Но эта асимметрия работает только при одном условии: каждый класс атак реально перекрыт хотя бы одним слоем. Если в архитектуре пропущен слой, который должен был ловить конкретный вектор, — для этого вектора у вас просто нет защиты, сколько бы других слоёв вы ни выстроили вокруг. Поэтому пропущенный слой — это не «чуть хуже»; это потенциальная дыра на всю стоимость актива.
И ещё один момент, о котором часто забывают: observability — часть безопасности. Если вы не видите, что кто-то пробует подбирать ваши API-ключи по брутфорсу, вы не заметите саму попытку. Аудит-логи, SIEM, алерты на аномальный доступ, мониторинг outbound traffic — всё это не «фичи безопасности», это основа.
KYC и AML
Прежде чем углубляться, расшифруем две аббревиатуры, которые в дальнейшем будут везде.
KYC — Know Your Customer, «знай своего клиента». Набор процедур, которыми биржа удостоверяется, что за каждым аккаунтом стоит реальный человек или компания: паспорт, селфи, подтверждение адреса проживания, иногда — источник средств. Цель — не дать анонимам, подсанкционным лицам и подставным аккаунтам открыть счёт.
AML — Anti-Money Laundering, «противодействие отмыванию денег». Эти процедуры анализируют поведение клиента после регистрации: отслеживают транзакции, ищут подозрительные паттерны, проверяют связь адресов с криминальными источниками и передают данные в финансовую разведку. Если KYC отвечает на вопрос «кто», то AML — на вопрос «что этот клиент делает с деньгами».
Эти два слоя работают в связке: KYC даёт идентичность, AML наблюдает за действиями этой идентичности во времени. По отдельности они дырявы — KYC без AML пропускает легально открытый счёт, через который потом гонят грязные деньги; AML без KYC видит подозрительные транзакции, но не может никому их пришить.
Этот раздел не о юриспруденции. Он о том, что KYC и AML — это не про «правила комплаенса», а про реальный инженерный слой, через который проходит каждый клиент и каждая транзакция. И если он сломан — биржа в лучшем случае получит штраф, в худшем — лишится лицензии в юрисдикции и потеряет доступ к фиатной инфраструктуре.
На высоком уровне система состоит из трёх слоёв:
- Идентификация на входе. Регистрация аккаунта, сбор документов, верификация паспорта и селфи, проверка адреса проживания, связь с санкционными списками (OFAC, UN, EU). Это — воронка KYC, обычно разделённая на уровни (tiers), где каждый уровень открывает новые возможности.
- Непрерывный мониторинг транзакций. Каждый депозит, вывод, внутренний перевод проходит через transaction monitoring: на какой адрес отправляется, связан ли он с известными рисковыми адресами и организациями (миксеры, darknet-маркеты, известные кошельки санкционных стран), не похож ли паттерн на структурирование (разбивку крупной суммы на мелкие для обхода порогов), не меняется ли поведение клиента резко.
- Отчётность и взаимодействие с регуляторами. Подача SAR (suspicious activity reports), ответы на запросы правоохранительных органов, выполнение travel rule (FATF Recommendation 16) — передача данных об отправителе и получателе при межбиржевых переводах выше определённого порога.
Для инженера здесь важны две вещи.
Первое — KYC не делается один раз. Он переходит в continuous re-KYC: если клиент не обновлял документы три года, биржа обязана попросить их заново. Если клиент вдруг начал получать депозиты с адресов, которые появились в санкционном списке после его регистрации — его нужно ограничить автоматически, а не через месяц, когда комплаенс-команда прочтёт отчёт. Всё это — отдельная очередь событий с отдельными SLA.
Второе — tiered KYC влияет на всю остальную систему. Лимиты торговли, доступность фиатных рельс, доступ к деривативам, возможность открывать sub-accounts — всё зависит от KYC tier. Эта информация используется десятком других подсистем, и её консистентность важна не меньше, чем консистентность баланса. Если один сервис видит пользователя как tier 2, а другой — как tier 0, это прямая дыра, через которую кто-нибудь обязательно пролезет.
Travel rule, санкционные проверки, мониторинг — всё это стоит денег и замедляет UX. Соблазн «упростить» огромен. Уроки последних десяти лет единообразны: биржи, которые не соблюдают KYC и AML на серьёзном уровне, либо сами исчезают под санкциями (BitMEX, затем более мелкие), либо лишаются банковских партнёров и не могут работать с фиатом. Комплаенс — не «налог на скорость», это инженерное требование, без которого продукт не существует в юрисдикциях, где живёт большинство пользователей.
Incident response: биржа не лжёт, биржа чинится
Хорошая биржа — не та, у которой никогда не бывает инцидентов, а та, которая умеет быстро их замечать, ограничивать ущерб и честно рассказывать о произошедшем.
Протокол incident response обычно состоит из четырёх фаз:
- Detection. Алерт пришёл. Кто на дежурстве — начинает работу. Первая задача — понять, что именно видно в мониторинге, и присвоить инциденту серьёзность (S1 — все клиенты не могут торговать, S2 — часть клиентов, S3 — деградация).
- Containment. Ограничить распространение сбоя. Для matching engine это может означать остановку торгов по одному символу, для wallet service — приостановку выводов в одну сеть, для API — более жёсткий rate limit. На этом этапе важнее сдержать ущерб, чем сразу найти первопричину.
- Remediation. Починить. Это может занять от минут до дней. Параллельно — коммуникация с клиентами: биржа, которая молчит во время инцидента, теряет доверие быстрее, чем биржа, которая честно пишет «работаем над проблемой». Статусная страница обновляется в реальном времени.
- Post-mortem. После закрытия инцидента команда публикует разбор: что произошло, почему и какие меры не дадут проблеме повториться. Хороший post-mortem содержит таймлайн, root cause analysis и список action items. Плохой ограничивается фразой «Dear customer, we experienced a technical issue».
Некоторые инциденты требуют особых действий, которые биржа должна иметь в арсенале заранее, а не придумывать на ходу:
- Halt a symbol. Остановить торги по одной паре. Все существующие ордера — либо остаются, либо отменяются (зависит от политики). Новые не принимаются. Это делается, когда на символе «что-то не то» — аномальная цена, подозрение на манипуляцию, проблема с оракулом.
- Trade bust. Отменить уже исполненные сделки и вернуть балансы. Это крайне спорная мера, допустимая только в вопиющих случаях — например, при fat-finger ошибке на порядок больше обычного объёма. Правила trade bust должны быть опубликованы заранее.
- Disclose a breach. Если произошёл взлом кошелька или утечка персональных данных — раскрыть публично. Быстро, честно, с оценкой ущерба. Попытки «подождать и понять масштаб» почти всегда оборачиваются тем, что новость выходит не от биржи, а от злоумышленника или от журналиста.
Incident response зависит не только от процесса, но и от культуры. Если инженеры боятся говорить об ошибках из-за возможного наказания, следующий инцидент обнаружат позже и устранят медленнее. Открытый разбор без поиска виноватых помогает быстрее признать проблему и сократить время восстановления.
Регулирование как архитектурное требование
Инженер CEX быстро узнаёт, что регулирование — это не «то, о чём думают юристы». Это вторая половина технического задания.
- Travel rule. При переводе между биржами выше определённой суммы (обычно $1 000 или $3 000 в зависимости от юрисдикции) отправляющая биржа должна передать получающей информацию об отправителе. Для этого биржа подключается к сетям вроде TRP, Sumsub, Notabene — и этот протокольный слой сидит в потоке вывода. Если он сломан — вывод заблокирован.
- Record retention. Все ордера, все сделки, все сообщения поддержки, все изменения KYC — должны храниться минимум 5–7 лет (зависит от юрисдикции). Это — прямое влияние на архитектуру базы данных и на стратегию холодного хранилища.
- Отчёты регуляторам. Ежедневные, ежемесячные, ежеквартальные выгрузки. Биржа должна уметь достоверно сформировать отчёт за любой период, и тот должен сходиться с внутренним леджером копейка в копейку.
- MiCA в ЕС, CFTC и SEC в США, FCA в Великобритании, MAS в Сингапуре. Каждая юрисдикция добавляет свои правила. Одни требуют отдельного юридического лица, другие — отдельного леджера для местных клиентов или отдельного API-endpoint. Глобальной бирже нужна не одна универсальная архитектура, а набор изолированных контуров для разных рынков.
Это влияет на архитектуру намного глубже, чем кажется на первый взгляд. Простой пример: если MiCA требует, чтобы клиентские фиатные средства хранились на отдельном счёте в отдельном банке (обычно так и есть), это значит, что внутренний леджер биржи должен уметь для каждого пользователя отслеживать, в каком именно банке находятся его евро — и уметь это отразить в отчёте.
Короткая мораль: регулятор — один из стейкхолдеров архитектуры, не «внешний раздражитель». Чем раньше инженер это примет, тем меньше будет переписываний в будущем.
Собираем всё вместе — и смотрим вперёд
Мы прошли девять разделов, каждый — про один кусок биржи: блокчейн-основы, философия, архитектура, matching engine, жизненный цикл ордера, леджер, кастоди, market data, эксплуатация. Это не лестница, по которой нужно подниматься снизу вверх, и не башня, где первый этаж держит на себе остальные. Это контур одной системы, в которой подсистемы взаимно опираются друг на друга. Matching engine не имеет смысла без журнала событий; журнал — без двойной записи; двойная запись — без сверки с on-chain-резервами; сверка — без сканера депозитов; сканер — без понимания, как блокчейн договаривается о консенсусе. Слабость в одной точке не остаётся локально: она протекает наружу и делает уязвимым всё, что от этой точки зависит.
У крупных крахов бирж за последнее десятилетие есть общая черта: критические ограничения системы оставались без контроля. Где-то не учитывали инварианты блокчейн-протокола. Где-то отсутствие двойной записи считали проблемой отчётности, хотя без неё нельзя надёжно вести саму биржу. Где-то multi-party control работал через один канал верификации и фактически превращался в single-party-схему. Выживали те площадки, чьи команды замечали такие разрывы и правильно реагировали на инциденты.
Цель этого гайда — чтобы, когда вы (или ваш AI-агент) будете строить, поддерживать, отлаживать или проектировать биржу, у вас была не «насмотренность», а структурированная модель. Модель, в которой каждый термин — matching engine, hot path, cold storage, double-entry, snapshot+delta, активная/пассивная пара, defense in depth — имеет не расплывчатое значение, а конкретное. Модель, в которой, услышав «у нас гэпы в стакане», вы автоматически вспоминаете раздел 8 и знаете, куда смотреть. Модель, в которой, услышав «у нас расходится баланс на $47», вы автоматически вспоминаете раздел 6 и знаете, какие счета сверять.
Что осталось за кадром: деривативы
Всё, о чём мы говорили, — спотовая торговля. Клиент покупает актив по текущей цене и владеет им. У современных бирж есть ещё деривативы: фьючерсы, бессрочные контракты (perpetual swaps), опционы, с плечом, с ликвидациями, с индексами финансирования. По объёму деривативы на большинстве крупных CEX превышают спот в несколько раз.
Архитектура деривативов добавляет целый новый ряд подсистем: risk engine с постоянной переоценкой позиций и маржи, liquidation engine, funding rate calculator, insurance fund, index price oracle — и всё это должно работать в той же hot-path-парадигме, что и matching engine. И граница между типами отказов там размывается: операционный сбой на деривативной бирже может мгновенно превратиться в кризис платёжеспособности, потому что невыплаченные ликвидации накапливают системный риск гораздо быстрее, чем расходящийся на копейки спот.
Дополнительное чтение
- FATF — Virtual assets and travel rule — основной регуляторный источник по AML-требованиям для CEX
- Kraken engineering blog — один из немногих публично доступных блогов, где инженеры CEX честно пишут об архитектуре, отказах и масштабировании
Если хочется конкретики по тому, как биржи разоряются на практике, четыре документа закрывают весь спектр причин:
- FTX Chapter 11 First Day Declaration (John J. Ray III, 2022-11-17) — как выглядит отсутствие бухгалтерии в продукте, который бухгалтерией должен быть
- DOJ press release on Bitfinex hacker sentencing (2024) — итог восьмилетнего расследования крупнейшего криптоизъятия в истории
- Wikipedia: 2024 WazirX hack — наиболее полный публичный свод источников по атаке Lazarus на индийскую биржу
- Ontario Securities Commission — QuadrigaCX report (PDF) — разбор того, как понци-схема маскировалась под биржу с «потерянным ключом»