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

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

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

В этой статье рассматривается практическая сторона вопроса: конфигурация, процесс запуска, выполняемые проверки и поведение при сбоях. Криптографические детали 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 (рассылка новых блоков)ДаДа

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

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

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

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

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

ПроверкаЧто предотвращает
state_rootПроизводитель записал состояние, которое не соответствует результатам выполнения транзакций. Охватывает балансы, HTLC-свопы, регистрации прекомпайлов и bridge_mints.
transactions_rootПроизводитель подменил, добавил или удалил транзакции из блока.
block_hashЛюбое поле в заголовке блока было изменено после его формирования.
parent_hashБлок некорректно ссылается на предыдущий. Это позволяет обнаруживать форки (разветвления цепи).
block_numberНаличие пропусков в последовательности (пропущенные блоки).
timestamp блокаБлок, помеченный временем раньше своего родителя. Timestamp-метки монотонны: производитель подтягивает их вперёд при обратном скачке часов, поэтому регрессия timestamp означает расходящийся или скомпрометированный upstream.
chain_idПовторное исполнение в другой сети (транзакция была подписана для другой сети).
Восстановление отправителяДля 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).

Все методы чтения работают в штатном режиме: 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 направляет подписанные записи в API записи производителя. Отдельный верификатор обслуживает чтение; Erlang distribution и admin API остаются закрытыми. Настройте маршруты по роли узла.

В аргументах VM релиза задайте -proto_dist inet_tls (или inet6_tls) и -ssl_dist_optfile с сертификатами, доверенными центрами сертификации и проверкой сертификата другой стороны. TLS должен быть настроен на всех участвующих узлах. Следуйте инструкции Erlang по TLS distribution и проверьте шифрование и аутентификацию соединения до подключения узла к другим участникам. Порты EPMD, distribution и администрирования оставьте закрытыми.

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

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

Производитель применяет то же правило для балансов. Если исполнение блока сделало бы баланс отрицательным или дробным, он ничего не фиксирует, прекращает выпуск блоков и сообщает о неисправности, пока оператор его не перезапустит.