Перейти к содержимому

Запуск верификатора

Верификатор (verifier) — это read-only узел, который воспроизводит каждый блок, полученный от производителя, на собственной локальной копии состояния. Если корень состояния (state root) или хеш блока (block hash) не совпадают, блок отклоняется, и узел прекращает обслуживание запросов. Standalone verifier обслуживает read-only RPC. Кошельки отправляют подписанные транзакции через публичный write RPC POA; verifier mode не пересылает запись.

В этой статье рассматривается практическая сторона вопроса: конфигурация, процесс запуска, выполняемые проверки и поведение при сбоях. Криптографические детали state roots и block hash подробно описаны в разделе State roots.

Для работы верификатора требуются два изменения в конфигурации по сравнению с производителем:

config/runtime.exs
config :chain,
mode: :verifier,
upstream_node: :"[email protected]"

Параметр mode: :verifier изменяет набор компонентов, которые запускаются при старте узла:

КомпонентПроизводитель (Producer)Верификатор (Verifier)
Инициализация генезисаДаНет (получает от upstream)
Производитель блоковДаНет
Пул транзакцийДаНет
Синхронизатор верификатораНетДа
RPC (eth_, /wallet/)ВнутреннийПубличный
BlockFeed (история блоков)ДаДа
BlockStage (live broadcast)ДаДа

Верификатору требуется собственная база данных. Общего хранилища с производителем не существует.

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

  1. Синхронизатор (syncer) подключается к upstream-узлу через Erlang distribution.
  2. Он запрашивает у upstream-узла текущий указатель (tip), затем скачивает блоки пакетами, начиная со своего последнего известного блока, и проверяет каждый из них (выполняет catch-up).
  3. Как только синхронизация завершена, верификатор подписывается на live-поток блоков (block feed).
  4. Для каждого последующего live-блока верификатор воспроизводит все транзакции, пересчитывает state root и block hash, и фиксирует блок только при совпадении обоих значений.
  5. Блоки, которые уже были зафиксированы на этапе catch-up, пропускаются по номеру.

Если upstream-узел недоступен при запуске, синхронизатор повторяет попытку подключения каждые 5 секунд.

Для каждого блока (включая генезис) верификатор независимо проверяет следующие параметры:

ПроверкаЧто предотвращает
state_rootПроизводитель записал состояние, которое не соответствует результатам выполнения транзакций. Охватывает балансы, HTLC-свопы, регистрации precompile-контрактов и bridge_mints.
transactions_rootПроизводитель подменил, добавил или удалил транзакции из блока.
block_hashЛюбое поле в заголовке блока было изменено после его формирования.
parent_hashБлок некорректно ссылается на предыдущий. Это позволяет обнаруживать форки (разветвления цепи).
block_numberНаличие пропусков в последовательности (пропущенные блоки).
timestamp блокаБлок, помеченный временем раньше своего родителя. Timestamp-метки монотонны: производитель подтягивает их вперёд при обратном скачке часов, поэтому регрессия timestamp означает расходящийся или скомпрометированный upstream.
chain_idCross-chain replay (транзакция была подписана для другой сети).
Восстановление отправителяДля Ethereum-транзакций адрес отправителя заново вычисляется из подписи. Для Tron-транзакций подпись перепроверяется на соответствие заявленному отправителю.
Инварианты генезисаTimestamp и transactions root генезис-блока должны совпадать с каноническими константами. Это предотвращает подделку генезиса.
Cross-chain проверка мостаКаждая строка bridge_mints независимо проверяется по финализированному состоянию Ethereum через JSON-RPC. Для строк bridge_lock адрес receiverOn2D из Ethereum-события Locked сравнивается с получателем HTLC в 2D. Полный список проверок приведен в статье Мост.

Расхождение в state_root, transactions_root или block_hash означает нарушение консенсуса. В этом случае верификатор останавливается и прекращает обслуживание запросов. Операционные ошибки (например, временная недоступность upstream-узла или пропуск блоков) инициируют повторный процесс catch-up.

Верификатор отклоняет RPC-вызовы, которые пытаются изменить состояние:

  • eth_sendRawTransaction возвращает код ошибки -32601.
  • /wallet/broadcasttransaction возвращает Tron-ошибку OTHER_ERROR (код 20).

Все read-only методы работают в штатном режиме: eth_getBalance, eth_getTransactionReceipt, eth_getBlockByNumber, /wallet/getaccount и другие. Кошельки и обозреватели блоков (explorers) могут подключаться к верификатору без каких-либо изменений.

Каждый верификатор ретранслирует проверенные блоки через свой собственный block feed. Второй верификатор может подписаться на этот поток вместо потока производителя:

# Верификатор B подключается к Верификатору A, а не к производителю
config :chain,
mode: :verifier,
upstream_node: :"[email protected]"

Каждый верификатор воспроизводит каждый блок самостоятельно, независимо от источника его получения. Модель безопасности остается неизменной: сначала проверь, потом обслуживай.

Producer ──▶ Верификатор A ──▶ Верификатор C
▶ Верификатор B ──▶ Верификатор D

Производитель и верификатор взаимодействуют через Erlang distribution. Используются два порта, оба должны быть защищены файрволом:

  • EPMD (по умолчанию порт 4369): Erlang Port Mapper Daemon.
  • Distribution port (настраивается через inet_dist_listen_min и inet_dist_listen_max в vm.args или конфигурации ядра): канал передачи данных, защищенный шифрованием TLS. Переменная окружения RELEASE_DISTRIBUTION управляет режимом дистрибуции (name/sname/none), а не портом.

Публичный edge направляет подписанные записи в producer write API. Standalone verifier обслуживает чтение; Erlang distribution и admin API остаются закрытыми. Настройте маршруты по роли ноды.

СценарийПоведение верификатора
Upstream недоступен при запускеПовторная попытка catch-up каждые 5 секунд.
Upstream-узел отключился в процессе работыLive-события прекращают поступать; reconnect и catch-up при восстановлении связи.
Пропуск блоков (gap)Автоматический catch-up через BlockFeed upstream-узла.
Расхождение state_rootОстановка. В логи записывается критическое предупреждение. RPC перестает отвечать.
Расхождение block_hashОстановка. В логи записывается критическое предупреждение. RPC перестает отвечать.
Отсутствие (nil) сырых данных транзакции в блокеТранзакция записывается как неуспешная (status 0), без падения узла.

Остановленный верификатор требует ручного вмешательства и анализа. Расхождение означает, что либо производитель скомпрометирован, либо в executor присутствует баг детерминизма. В обоих случаях необходимо участие инженера.