Обзор

Private Channels не прошёл аудит безопасности и не рекомендуется для использования в продакшене с реальными средствами без тщательной проверки безопасности.

Развёртываете экземпляр? Перейдите к руководству оператора. Интегрируетесь с существующим экземпляром? Перейдите к Quickstart. Эта страница является справочником по архитектуре для обеих аудиторий.

Архитектура

Private Channels состоит из четырёх компонентов: двух on-chain программ Solana (Escrow и Withdraw) и двух off-chain сервисов (Gateway и Auth Service). Вместе они образуют протокол state channel, где средства хранятся в Mainnet, а переводы проводятся вне цепочки.

Программа Escrow

Программа Escrow — это on-chain программа Solana, которая хранит депонированные SPL-токены. Она является якорем доверия системы: все средства в конечном счёте находятся в эскроу до тех пор, пока оператор не предоставит корректное доказательство исключения Sparse Merkle Tree для их освобождения.

  • ID программы: 9tgHa1DcnaSSUtmMsst8ovKTe1Gfxzezn27KnH9xXYeU
  • Этот ID компилируется в бинарный файл программы через declare_id!(). Off-chain сервисы считывают тот же ID во время компиляции из сгенерированного клиентского крейта, а не из переменной окружения.
  • Управляет PDA-аккаунтами Instance, AllowedMint и Operator
  • Инструкции: CreateInstance, AllowMint, BlockMint, AddOperator, RemoveOperator, SetNewAdmin, Deposit, ReleaseFunds, ResetSmtRoot

Программа Withdraw

Программа Withdraw работает в сети приватного канала, а не в Solana Mainnet. Пользователи вызывают WithdrawFunds для сжигания баланса токенов на стороне канала. Это сжигание не освобождает средства автоматически; оно сигнализирует оператору о том, что запрос на вывод ожидает обработки. Затем оператор вызывает ReleaseFunds в программе Escrow с корректным SMT-доказательством для завершения расчёта.

  • ID программы: J231K9UEpS4y4KAPwGc4gsMNCjKFRMYcQBcjVW7vBhVi
  • Этот ID компилируется в бинарный файл программы. Off-chain сервисы считывают тот же ID во время компиляции из сгенерированного клиентского крейта, а не из переменной окружения.

Gateway

Gateway — это совместимый с Solana JSON-RPC прокси, который направляет клиентские запросы к write-узлу сети канала (для отправки транзакций) и read-узлу (для запросов). Настраивается через переменные окружения: GATEWAY_PORT, GATEWAY_WRITE_URL, GATEWAY_READ_URL.

Эндпоинты проверки работоспособности (аутентификация не требуется):

  • GET /health — проверка доступности; возвращает 200 {"status":"ok"}
  • GET /ready — глубокая проверка готовности, опрашивает write- и read-узлы; возвращает 200 {"status":"ready"} или 503 {"status":"degraded"}

Маршрутизация RPC-методов и управление доступом

Gateway направляет sendTransaction к write-узлу, а все остальные методы — к read-узлу. Запросы размером более 64 КБ отклоняются с HTTP 413. При включённой аутентификации доступ к методам контролируется JWT-ролью. Полную матрицу методов см. в разделе Аутентификация и роли.

Auth Service

Auth Service — это опциональный компонент, который выдаёт HS256 JWT (срок действия 24 часа) для контроля доступа к gateway. Он активируется при наличии переменной окружения JWT_SECRET. Без неё gateway принимает все подключения.

JWT-клеймы: sub (UUID пользователя), role ("user" или "operator"), iss ("private-channel-auth"), aud ("private-channel-gateway"), exp (Unix-временная метка). iss и aud проверяются конфигурацией JWT в gateway, а не десериализуются в структуру клеймов приложения: на уровне приложения доступны только sub, role и exp.

Роли:

  • user — доступ ограничен собственными верифицированными кошельками; не может вызывать getBlock, getTransaction или simulateTransaction
  • operator — обходит все проверки владения; полный доступ ко всем RPC-методам; должен быть предоставлен в базе данных (самостоятельное повышение привилегий невозможно)

Streamer

Streamer — это WebSocket-сервер, который в реальном времени передаёт обновления состояния канала подключённым клиентам, исключая необходимость опроса RPC. Он опрашивает PostgreSQL на наличие изменений состояния. Streamer входит в базовый стек Docker Compose, а не в devnet-стек, который развёртывается в этом руководстве; см. справочник конфигурации.

  • Порт: 8902, настраивается через STREAMER_PORT
  • Подключение: ws://localhost:8902
  • Эндпоинт проверки работоспособности: GET /health — возвращает 503, если любой внутренний цикл опроса зависает дольше 30 секунд

Схема событий WebSocket ещё не задокументирована публично. До появления официальной документации обращайтесь к core/src/bin/streamer.rs для получения подробностей реализации.

Конвейер транзакций

Transaction -> [1:Dedup] -> [2:SigVerify] -> [3:Sequencer] -> [4:Executor] -> [5:Settler] -> Database

Транзакции, отправленные в Gateway, проходят через пятиэтапный конвейер, прежде чем их состояние будет зафиксировано:

  1. Dedup — фильтрует дублирующиеся транзакции до их поступления в конвейер
  2. SigVerify — проверяет подписи транзакций по открытому ключу подписанта
  3. Sequencer — детерминированно упорядочивает корректные транзакции для формирования канонической истории
  4. Executor — выполняет транзакции против уровня аккаунтов канала (BOB Cache + AccountsDB), обновляя балансы вне цепочки
  5. Settler — фиксирует накопленные результаты транзакций в PostgreSQL и обновляет кэш Redis; генерирует новые blockhash для следующего цикла блоков. Расчёт в Mainnet (вызов ReleaseFunds) обрабатывается отдельно сервисом operator-private-channel

Ключевые особенности

Конфиденциальность

Переводы между участниками канала не фиксируются в Solana Mainnet. На блокчейне отображаются только депозиты (вход в канал) и окончательные выводы средств (выход из канала). Идентификаторы контрагентов и суммы переводов не видны сторонним наблюдателям во время работы канала.

Производительность

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

Расчёт

Каждый вывод средств защищён on-chain доказательством Sparse Merkle Tree. Корень SMT хранится в Instance.withdrawal_transactions_root в программе Escrow. При вызове ReleaseFunds программа сначала проверяет доказательство исключения для неиспользованного нонса относительно текущего on-chain корня, затем проверяет отдельное доказательство включения для этого нонса относительно нового корня, предоставленного вызывающей стороной. Только после прохождения обеих проверок новый корень сохраняется, что делает двойное расходование невозможным даже в случае компрометации ключа оператора.

Модель безопасности

Ключ администратора — управляет созданием экземпляров (CreateInstance) и провижнингом операторов (AddOperator / RemoveOperator). Компрометация ключа администратора позволяет произвольно добавлять операторов. SetNewAdmin передаёт права администратора необратимо за один шаг; защищайте ключ администратора соответствующим образом.

Ключи операторов — могут вызывать ReleaseFunds и ResetSmtRoot. Они не могут освобождать средства без корректного доказательства исключения SMT относительно текущего on-chain корня. On-chain проверка verify_smt_exclusion_proof — последний рубеж защиты от несанкционированных выводов: одной лишь компрометации ключа оператора недостаточно для опустошения эскроу.

Корень SMT — хранится on-chain в Instance.withdrawal_transactions_root. Атомарно обновляется при каждом вызове ReleaseFunds. Поскольку каждое доказательство должно ссылаться на неиспользованный нонс, двойное расходование одного и того же баланса канала невозможно даже в случае компрометации ключа оператора.

Ротация дерева — Instance.current_tree_index отслеживает epoch'и дерева. При вызове ResetSmtRoot индекс дерева увеличивается, а все нонсы из предыдущего epoch'а дерева аннулируются, что обеспечивает чистое состояние для новых расчётных циклов.

Операционная безопасность ключей

Off-chain сервисы используют собственный набор ключей подписантов, не связанный с on-chain полномочиями администратора/оператора, описанными в разделе «Модель безопасности» выше. ADMIN_PRIVATE_KEY обязателен для каждого сервиса оператора и оплачивает комиссии за транзакции; отдельный, необязательный OPERATOR_PRIVATE_KEY предоставляет on-chain подпись Operator для ReleaseFunds и ResetSmtRoot и использует значение ADMIN_PRIVATE_KEY, если не задан. Никогда не помещайте ключ администратора экземпляра на уровне протокола (используемый для CreateInstance / AddOperator / SetNewAdmin) ни в одну из этих переменных и не раскрывайте его во время выполнения; держите этот ключ в холодном хранилище и офлайн.

ReleaseFunds и ResetSmtRoot требуют двух on-chain подписей: плательщика комиссии (из ADMIN_PRIVATE_KEY) и авторизующей стороны PDA Operator (из OPERATOR_PRIVATE_KEY, или ADMIN_PRIVATE_KEY, если первый не задан). В devnet-инструкции этого руководства сгенерированный keypair оператора помещается в ADMIN_PRIVATE_KEY, а OPERATOR_PRIVATE_KEY остаётся незаданным, так что один и тот же keypair выполняет обе роли подписанта. Обращайтесь с ключом, находящимся в ADMIN_PRIVATE_KEY, с теми же мерами предосторожности, что и с приватным ключом горячего кошелька:

  • Храните его только в файле .env, добавленном в gitignore, никогда — в .env.devnet или в каком-либо зафиксированном конфиге
  • Для продакшен-развёртываний рассмотрите использование менеджера секретов (AWS Secrets Manager, HashiCorp Vault) вместо переменной окружения в открытом виде
  • keypair администратора экземпляра на уровне протокола (используемый для вызова AddOperator / SetNewAdmin) следует держать в холодном хранилище; он нужен только при настройке экземпляра и провижнинге операторов, но не во время выполнения

SetNewAdmin передаёт права администратора необратимо в одной транзакции: текущий администратор не имеет пути к восстановлению без содействия нового администратора. Не вызывайте эту инструкцию, не проверив целевой адрес.

Следующие шаги

Is this page helpful?