State roots: верификация сети без валидаторов
Большинство блокчейнов полагаются на набор валидаторов для достижения консенсуса о текущем состоянии. В 2D работает только один производитель блоков. Это делает создание блоков быстрым и простым процессом, но одновременно означает отсутствие встроенного механизма «второго мнения». Если производитель скомпрометирован, он потенциально может записать в блокчейн любое состояние.
Механизм state roots (корней состояния) решает эту проблему. После каждого блока производитель вычисляет криптографический отпечаток всего изменяемого состояния и фиксирует его в хеше блока. Любой клиент, который повторно выполняет транзакции блока, отталкиваясь от предыдущего состояния, может независимо пересчитать этот отпечаток и сравнить его с заявленным. Несовпадение означает, что производитель предоставил ложные данные. Таким образом, необходимость в наборе валидаторов отпадает.
Что входит в state root
Заголовок раздела «Что входит в state root»В вычислении участвуют четыре таблицы из схемы состояния, отсортированные по первичному ключу. Каждая строка хешируется с помощью алгоритма keccak256, после чего результаты объединяются:
- accounts (address, balance, nonce). Каждый аккаунт, который когда-либо получал USD-stable.
- htlc_swaps (hash, sender, receiver, amount, deadline, status, preimage). Каждый активный или завершенный атомарный своп.
- precompiles (address, name, handler, enabled). Набор зарегистрированных precompile-контрактов.
- bridge_mints (eth_event_id, исходная тройка, amount, координаты блока, htlc_hash). По одной строке на каждое событие Ethereum
Locked, под которое оператор выполнил пополнение (refill). Включение этой таблицы строго обязательно: без нее инвариант дедупликации существовал бы только на уровне базы данных, и производитель мог бы дважды выпустить токены под одно и то же событие, при этом успешно пройдя проверку replay у честного верификатора. Cross-chain проверка, которая перепроверяет каждую строку, подробно описана в статье про мост.
Таблица blocks_tip (singleton, кеш текущего указателя на голову цепи) намеренно исключена. Это кеш для быстрого доступа, а не часть консенсусного состояния. Ее включение создало бы циклическую зависимость: state root необходимо вычислять до обновления tip, но обновление tip происходит в рамках той же транзакции.
Сам корень вычисляется следующим образом:
accounts_root = keccak256( row_hash(account_1) || row_hash(account_2) || ... )htlc_root = keccak256( row_hash(swap_1) || row_hash(swap_2) || ... )precompiles_root = keccak256( row_hash(precompile_1) || ... )bridge_mints_root = keccak256( row_hash(mint_1) || ... )
state_root = keccak256( accounts_root || htlc_root || precompiles_root || bridge_mints_root )Каждый хеш строки (row hash) кодирует поля в каноническом бинарном формате (целые числа фиксированной ширины, строки с префиксом длины, нормализованные значения Decimal). Два арифметически равных баланса всегда дают одинаковую последовательность байт, независимо от внутреннего представления объекта Decimal.
Как block hash фиксирует state root
Заголовок раздела «Как block hash фиксирует state root»Хеш каждого блока формируется так:
block_hash = keccak256( block_number (8 байт) || parent_hash (32 байта) || timestamp (8 байт) || tx_root (32 байта, хеш всех tx hash в порядке выполнения) || state_root (32 байта, корень отсортированных хешей, описанный выше))Любое изменение баланса, статуса HTLC или регистрации precompile-контракта без соответствующей валидной транзакции изменяет state root, затем block hash, а затем и цепочку parent_hash для всех последующих блоков. Одно несанкционированное изменение строки каскадно приводит к легко обнаруживаемому разрыву цепи.
Что верификатор с этим делает
Заголовок раздела «Что верификатор с этим делает»В составе сети работает независимый клиент-верификатор (verifier). Для каждого блока он выполняет следующие шаги:
- Получает блок (заголовок + сырые подписанные транзакции) от upstream-узла.
- Повторно выполняет транзакции на своей локальной копии предыдущего состояния.
- Вычисляет собственный state root.
- Сравнивает его с корнем, заявленным производителем блоков.
- При совпадении: фиксирует блок в базе и предоставляет проверенное состояние кошелькам и RPC-клиентам.
- При несовпадении: откатывает транзакцию, логирует критическое предупреждение и прекращает обслуживание запросов.
Верификатор работает как отдельный BEAM-узел со своей собственной базой данных. У него нет прямого доступа к хранилищу производителя. Пользователи подключаются к RPC верификатора, а не производителя. Несколько верификаторов могут работать независимо друг от друга. Запустить собственный верификатор может любой желающий.
Верификатор, успешно зафиксировавший блок, ретранслирует его через свой собственный block feed. Другие верификаторы могут подписаться на этот поток вместо потока производителя. Таким образом, верификаторы могут выстраиваться в цепочку или древовидную структуру:
Producer ──▶ Верификатор A ──▶ Верификатор C ▶ Верификатор B ──▶ Верификатор DКаждый верификатор проверяет каждый блок независимо, независимо от источника его получения. Модель доверия остается неизменной: сначала проверь, потом обслуживай.
Как блоки и транзакции перемещаются между узлами
Заголовок раздела «Как блоки и транзакции перемещаются между узлами»Производитель и верификатор — это два BEAM-узла (Erlang VM), соединенных через механизм Erlang distribution. Все данные передаются по единому зашифрованному каналу. В архитектуре не используются HTTP-запросы или внешние очереди сообщений.
Блоки (от производителя к верификаторам): после фиксации (коммита) каждого блока в базе данных производитель публикует событие через конвейер GenStage. GenStage — это библиотека Elixir для организации потоков producer-consumer с поддержкой backpressure. Каждый подписанный верификатор получает каждый блок (используется broadcast fan-out, а не round-robin). Если верификатор отстает или отключается, производитель буферизирует до 1000 блоков; более старые блоки удаляются из буфера.
При запуске (или после разрыва соединения) верификатор сначала синхронизирует историю (catch-up), а затем подписывается на live-поток. Он запрашивает у upstream-узла текущий указатель (tip), затем скачивает блоки пакетами, начиная со своего последнего известного блока, и проверяет каждый из них. Как только синхронизация завершена, верификатор переключается на live-подписку GenStage. Этот механизм работает одинаково независимо от того, является ли upstream-узлом производитель или другой верификатор.
Событие блока содержит заголовок и сырые подписанные транзакции в порядке исполнения. Verifier восстанавливает отправителя Ethereum из подписанного RLP и повторно проверяет подпись Tron по protobuf и заявленному отправителю.
Отправка транзакций: публичный wallet RPC принимает подписанные транзакции через write-путь producer. Отдельный verifier работает только на чтение: eth_sendRawTransaction и отправка Tron отклоняются в verifier mode. Для отправки используйте публичный RPC POA.
Публичный edge открывает только разрешённые RPC и API. Закрытые node и admin listeners остаются за сетевой границей. Read-only verifier и публичный write RPC — разные роли deployment.
Дополнительные верификаторы могут подключаться к первому верификатору вместо прямого соединения с производителем. Каждый верификатор ретранслирует проверенные блоки, поэтому топология сети может представлять собой звезду, цепочку или дерево в зависимости от схемы развертывания.
Текущий подход и масштабирование
Заголовок раздела «Текущий подход и масштабирование»Текущая реализация использует метод sorted-hash: извлечение всех строк из каждой таблицы состояния, сортировка по первичному ключу, хеширование и конкатенация. Вычислительная сложность составляет O(n) на блок по всему объему состояния. Для сети с количеством аккаунтов менее миллиона такой производительности достаточно.
По мере роста объема состояния планируется миграция на инкрементальное дерево Меркла (Merkle tree), которое обновляет только те строки, которые изменились в текущем блоке. В этом случае вычислительная стоимость на блок снизится до O(k log n), где k — количество измененных строк. Подобная миграция является изменением правил консенсуса: потребуется версионированное обновление (rollout), чтобы каждый верификатор вычислял идентичный корень для одного и того же блока.
Почему это важно для cross-chain мостов
Заголовок раздела «Почему это важно для cross-chain мостов»Первый реальный потребитель этого механизма — мост (bridge). При каждом пополнении (refill), инициированном оператором, верификатор независимо запрашивает информацию об указанном Ethereum-событии через локальный сайдкар helios и отклоняет блок, если данные не совпадают. State roots делают такую проверку возможной: они предоставляют каждому клиенту верифицируемую картину того, что действительно произошло on-chain. Строка bridge_mints с исходной тройкой данных, идентификатором дедупликации и суммой хешируется в блокчейн, и скомпрометированный производитель не может незаметно изменить ее задним числом.