Плагин валидатора
Плагин валидатора отвечает за подписание и производство блоков. Он запускает выделенный цикл таймера с интервалом 250 мс в собственном потоке ОС, выполняет ряд проверок безопасности при каждом тике и вызывает database::generate_block() при выполнении всех условий.
Источник: plugins/validator/validator.cpp
Зависимости
chain::plugin, p2p::p2p_plugin, snapshot::snapshot_pluginКонфигурация
Производство блоков
| Параметр | По умолчанию | Описание |
|---|---|---|
validator / -w | — | Имя/имена аккаунтов-валидаторов; может повторяться |
private-key | — | WIF-ключ(и) для подписи; может повторяться |
emergency-private-key | — | WIF-ключ для экстренного консенсуса; автоматически добавляет CHAIN_EMERGENCY_VALIDATOR_ACCOUNT в набор валидаторов |
enable-stale-production | false | Обход проверок участия и синхронизации (только для тестовой сети / восстановления сети) |
required-participation | 3300 | Минимальное участие валидаторов в базисных пунктах (3300 = 33%) |
fork-collision-timeout-blocks | 21 | Количество последовательных отсрочек при коллизии форков перед принудительным производством (один полный раунд валидаторов) |
Синхронизация NTP
| Параметр | По умолчанию | Описание |
|---|---|---|
ntp-server | pool.ntp.org, time.google.com, time.cloudflare.com | NTP-серверы; может повторяться |
ntp-request-interval | 900 | Нормальный интервал синхронизации в секундах |
ntp-retry-interval | 300 | Интервал повторной попытки при отсутствии ответа NTP |
ntp-round-trip-threshold | 150 | Отбрасывать ответы NTP с временем roundtrip > N мс |
ntp-history-size | 5 | Окно скользящего среднего для сглаживания дельты NTP |
Отладка
| Параметр | По умолчанию | Описание |
|---|---|---|
debug-block-production | false | Включить подробное отладочное логирование в базе данных цепочки |
Таймер производства
Цикл производства работает на выделенном production_io_service_ и собственном потоке ОС — полностью отдельно от общего io_service AppBase/P2P. Это предотвращает задержку 250-мс обратного вызова таймера из-за P2P-активности (отключение пиров, TLS-рукопожатия, опустошение очереди отправки).
Выравнивание тика:
тик таймера каждые 250 мс по границам 250 мс настенных часов
минимальная задержка: 50 мс (для поглощения джиттера ОС)Упреждение: now = ntp_time + 250 мс — сдвигает решение о производстве вперёд так, чтобы тик на T_slot - 250 мс совпадал точно с границей слота:
Слот в T=6.000 с:
Тик в T=5.750 → now=6.000 → слот совпал → производство при lag=0 мс
Тик в T=6.000 → now=6.250 → lag=250 мс → ещё в пределах порога 500 мсПропуск при задержке: После результата lag тот же слот повторно срабатывал бы при каждом тике в оставшееся время 3-секундного интервала. Защита пропускает до следующей границы слота, чтобы цикл освобождал CPU вместо холостого вращения.
maybe_produce_block() — последовательность проверок безопасности
Следующие проверки выполняются по порядку при каждом тике, где slot > 0.
| № | Проверка | Результат при отказе |
|---|---|---|
| 1 | Шлюз синхронизации DLT (только DLT-режим): chain().is_syncing() = false, или узел является экстренным мастером | not_synced |
| 2 | Шлюз паузы снапшота: snapshot().is_snapshot_in_progress() = false | not_synced |
| 3 | Шлюз нагона P2P: p2p().is_catching_up_after_pause() = false | not_synced |
| 4 | Трёхсостояние безопасности HF12 (см. ниже) | not_synced / low_participation |
| 5 | slot = db.get_slot_at_time(now) > 0 | not_time_yet |
| 6 | Запланированный валидатор входит в наш настроенный набор | not_my_turn |
| 7 | Слот ещё не заполнен (scheduled_time > head_block_time) | not_time_yet |
| 8 | signing_key валидатора в блокчейне ненулевой | not_my_turn |
| 9 | Приватный ключ для signing_key загружен | no_private_key |
| 10 | До HF12: участие ≥ порога | low_participation |
| 11 | ` | scheduled_time - now |
| 12 | Проверка коллизии форков (см. ниже) | fork_collision |
| 13 | Вторая проверка паузы снапшота (окно гонки) | not_time_yet |
| 14 | db.generate_block() + p2p().broadcast_block() | produced |
Трёхсостояние безопасности HF12 (проверка №4)
Активен экстренный консенсус:
- Экстренный мастер (имеет
emergency-private-key+ «committee» в расписании): продолжает безусловно. - Ведомый: требует
get_slot_time(1) >= now(цепочка не устарела) перед производством.
Нормальный режим (HF12+):
- Участие ≥ 33%: здоровая сеть; проверка синхронизации через
get_slot_time(1). - Участие < 33%: ослабленная сеть; применяется порог участия vs
required-participation. enable-stale-production=true: обходит обе проверки участия и синхронизации.
До HF12: Простая проверка синхронизации через get_slot_time(1).
Разрешение коллизии форков (проверка №12)
При наличии конкурирующего блока на head_block_num + 1:
- Сравнение по весу голосов (HF12+):
compare_fork_branches()вычисляет суммарные SHARES, делегированные каждой ветви. Если наша ветвь тяжелее — продолжить и удалить конкурирующий блок. При равенстве или меньшем весе — отложить. - Таймаут зависшей головы: После
fork-collision-timeout-blocksпоследовательных отсрочек (по умолчанию 21 = 63 секунды) конкурирующий блок удаляется и производство возобновляется. Это обрабатывает «мёртвые» блоки форка от отключённых пиров.
Экстренный режим: Любой конкурирующий блок вызывает отсрочку; путь взвешивания голосов не используется.
Обнаружение minority fork
Перед каждой попыткой производства (после проверок безопасности HF12) плагин просматривает последние 21 блок в fork_db. Если все 21 были произведены собственными настроенными валидаторами узла — узел изолирован на minority fork.
- Действие по умолчанию: Вызов
p2p().resync_from_lib()— откат блоков к LIB, сброс fork DB, повторная инициация синхронизации P2P, переподключение к начальным узлам. Возвращаетminority_fork. - С
enable-stale-production=true: Запись предупреждения, продолжение производства. - Пропускается при: Активном экстренном консенсусе (блоки комитета всегда соответствовали бы нашему настроенному набору). В экстренном режиме вместо него используется специфичная для DLT проверка изоляции ведомого.
Обнаружение остановки NTP
Если get_slot_at_time(now) возвращает 0 (NTP отстаёт от времени цепочки), счётчик _slot_zero_streak увеличивается:
| Серия | Время | Действие |
|---|---|---|
| 3 | ~750 мс | Предупреждение |
| 10 | ~2,5 с | Принудительная повторная синхронизация NTP |
| 60 | ~15 с | Предупреждение о длительной остановке |
| 120 | ~30 с | Критическая ошибка |
Счётчик сбрасывается при любом ненулевом результате слота.
Watchdog производства
Если узел хотя бы раз производил блок и should_be_producing = true (определяется из активного состояния цепочки: участие ≥ 33% или активен экстренный консенсус с нашим ключом), но ни одного блока не было произведено в течение:
- Экстренный мастер: 60 секунд
- Обычный валидатор: 180 секунд
Watchdog срабатывает каждые 30 секунд и записывает диагностику. При выполнении условий восстановления (голова продвигалась в последние 30 с, не синхронизируется, есть соединения с пирами, ненулевые ключи подписи в блокчейне) он принудительно сбрасывает блокирующие условия:
- Сбрасывает флаг
_minority_fork_recovering. - Вызывает
p2p().clear_catchup_flag()— сбрасывает флаг нагона P2P после паузы. - Вызывает
chain().clear_syncing()— сбрасывает флаг синхронизации цепочки.
Производство автоматически возобновляется при следующем тике.
on_block_applied() — обработчик сигнала
Подключён к database::applied_block. Запускается для каждого входящего блока.
Обнаружение пропущенного слота
Когда block_num > prev_num + 1 (разрыв в потоке блоков), обработчик определяет, был ли наш валидатор запланирован для любого из пропущенных слотов, и записывает полное диагностическое состояние (флаги производства, смещение NTP, статус синхронизации, статус ключа подписи, время следующего слота).
Обнаружение перехвата слота (DLT-режим экстренного консенсуса)
При активном экстренном консенсусе экстренный мастер может обнулить ключ подписи нашего валидатора и производить блоки комитета в наших запланированных слотах. Обработчик отслеживает это через _slot_hijack_count. Сбрасывается, когда один из наших валидаторов производит блок.
Публичный API
is_witness_scheduled_soon()
Возвращает true, если локально управляемый валидатор запланирован для производства в следующих 4 слотах (~12 секунд). Плагин снапшота вызывает это перед планированием снапшота, чтобы отложить его при неминуемом производстве.
is_emergency_master()
Возвращает true, когда:
- Настроен
emergency-private-key(CHAIN_EMERGENCY_VALIDATOR_ACCOUNTв_validators). - Аккаунт «committee» находится в текущем расписании валидаторов.
Только узлы, где выполняются оба условия, должны производить блоки в одиночку в экстренном режиме; остальные являются ведомыми и должны сначала синхронизироваться.
is_emergency_key_configured()
Возвращает true, если настроен emergency-private-key, независимо от текущего расписания. Используется в сообщениях приветствия P2P (поле has_emergency_key).
get_production_diagnostics()
Возвращает компактную диагностическую строку:
validator[skip_flags=0x0 catching_up=0 head=#79881136 last_prod=45s_ago minority_rcv=0 slot_hijacks=0]Включается в логи стагнации P2P FORWARD, когда узел завис без пиров впереди.
Ключевые инварианты
- Никогда не производить в DLT-режиме при синхронизации — создаёт блоки на устаревшей голове, вызывая осцилляцию форков.
- Никогда не производить во время создания снапшота — взаимная блокировка записи.
- Никогда не производить, если слот уже заполнен — создаёт мини-форк.
- Экстренный мастер должен всегда производить — он единственный производитель блоков; ожидание вызовет дедлок.
- Ведомые должны синхронизироваться перед производством в экстренном режиме — производство на устаревшей голове = minority fork.
- Участие < 33% останавливает производство — защита от сетевого раздела (переопределяемая).
- 21 последовательный блок от собственных валидаторов → откат к LIB — восстановление после minority fork.
- Все чтения базы данных свежие — кэширование состояния отсутствует; экстренный режим может активироваться/деактивироваться на каждом блоке.
Устранение неполадок
| Симптом | Что проверить |
|---|---|
Логи not_synced | Активна синхронизация DLT или создание снапшота — ждать; watchdog сбросит автоматически при зависании |
Повторное not_time_yet | NTP отстаёт от времени цепочки; проверьте предупреждения _slot_zero_streak и смещение NTP |
not_my_turn на нашем слоте | Ключ подписи обнулён в блокчейне; отправьте validator_update_operation для восстановления |
no_private_key | В конфиге отсутствует private-key для ключа подписи, зарегистрированного в блокчейне |
low_participation | Участие сети < 33%; проверьте подключение к пирам или установите enable-stale-production=true |
fork_collision | Конкурирующий блок на следующей высоте; ждать разрешения по весу голосов или таймаута 21 отсрочки |
minority_fork | Изолирован; плагин автоматически пересинхронизируется с LIB |
| Watchdog срабатывает повторно | Флаг синхронизации или нагона завис; watchdog сбросит автоматически при продвижении головы |
Логи SLOT-HIJACK | Экстренный мастер обнулил наш ключ; восстановите через validator_update_operation |
См. также: Validator Guard, Fair-DPOS, Экстренный консенсус, Обработка блоков.