Режим экстренного консенсуса
Режим экстренного консенсуса (введён в HF12) активируется автоматически, когда сеть простаивает в течение 1 часа. Специальный валидатор "committee" берёт на себя производство блоков для поддержания непрерывности цепочки до тех пор, пока реальные валидаторы не восстановят свои ключи подписи.
Ключевые константы
| Константа | Значение | Значение |
|---|---|---|
CHAIN_EMERGENCY_CONSENSUS_TIMEOUT_SEC | 3600 с | Время простоя до активации |
CHAIN_EMERGENCY_VALIDATOR_ACCOUNT | "committee" | Аккаунт экстренного производителя блоков |
CHAIN_EMERGENCY_VALIDATOR_PUBLIC_KEY | VIZ75CR... | Детерминированный ключ подписи для экстренного режима |
CHAIN_EMERGENCY_EXIT_NORMAL_BLOCKS | 21 | Последовательные блоки реальных валидаторов для выхода |
CHAIN_IRREVERSIBLE_THRESHOLD | 75% | Доля слотов расписания для выхода |
CHAIN_MAX_VALIDATORS | 21 | Максимальное количество слотов валидаторов |
Поля состояния в dynamic_global_property_object
| Поле | По умолчанию | Значение |
|---|---|---|
emergency_consensus_active | false | Активен экстренный режим |
emergency_consensus_start_block | 0 | Номер блока при активации |
Активация
update_global_dynamic_data() выполняется при каждом применённом блоке и проверяет:
- Ворота HF12 — пропустить, если харфорк ещё не активирован.
- Уже активен — пропустить, если экстренный режим уже включён.
- LIB-блок доступен в block log — пропустить, если LIB-блок отсутствует в block log (DLT-узлы после восстановления из снимка имеют пустой block log; отсутствие LIB-блока вычислило бы миллионы устаревших секунд и запустило бы ложный дедлок активации).
- Таймаут — вычислить
seconds_since_lib = current_block.timestamp − lib_block.timestamp. Если< 3600, пропустить.
Все проверки используют только временны́е метки, встроенные в блоки — никаких системных часов, никаких флагов пропуска. Это гарантирует идентичную детерминированную активацию на каждом узле, включая воспроизведения.
Последовательность активации
Когда пересекается порог таймаута:
- Установить
dgp.emergency_consensus_active = trueиdgp.emergency_consensus_start_block = block_num. - Создать или обновить объект валидатора "committee":
signing_key = CHAIN_EMERGENCY_VALIDATOR_PUBLIC_KEYprops = current_median_props- Голоса за харфорки установлены на текущую применённую версию (нейтральный голосователь).
- Отключить ВСЕХ реальных валидаторов: установить
signing_key = zero, сброситьpenalty_percent = 0,current_run = 0. - Удалить все объекты
witness_penalty_expire. - Переопределить расписание валидаторов: все
CHAIN_MAX_VALIDATORSслотов → "committee". - Уведомить fork DB:
_fork_db.set_emergency_mode(true)(включает детерминированное разрешение хэш-ничьих). - Лог:
"EMERGENCY CONSENSUS MODE activated at block #N. No blocks for X seconds since LIB Y."
Экстренная работа
Производство блоков — мастер vs. последователь
Узлы с настроенным emergency-private-key в config.ini являются экстренными мастерами; все остальные узлы — последователи.
# Только для экстренного мастер-узла
emergency-private-key = 5Jzzz... # приватный ключ CHAIN_EMERGENCY_VALIDATOR_ACCOUNT| Роль | Проверка DLT-синхронизации | Проверка minority fork | Производство |
|---|---|---|---|
| Мастер | Обходится (привело бы к дедлоку) | Пропускается (ожидается, что все блоки будут "нашими") | Производит блоки для всех слотов |
| Последователь | Нормальная | 21-блочная проверка (1 полный раунд) | Стандартное производство для собственных запланированных слотов |
Детерминированное разрешение ничьих в fork DB
В экстренном режиме несколько мастеров (географическая избыточность) могут производить конкурирующие блоки на одной высоте с одним ключом. Порядок прибытия варьируется в зависимости от P2P-топологии.
fork_database::_push_block() разрешает ничьи:
item->num == _head->num И _emergency_consensus_active:
item->id < _head->id → _head = item (побеждает меньший хэш блока)
иначе → оставить текущую _headВсе узлы сходятся на одной вершине цепочки независимо от того, какой блок они увидели первым.
Продвижение LIB
update_last_irreversible_block() вычисляет LIB нормально, но ограничивает его значением HEAD − 1 в экстренном режиме. Без этого ограничения, поскольку все 21 слот принадлежат "committee" и committee.last_supported_block_num == HEAD, вычисление nth_element возвращает HEAD — что зафиксировало бы сессию отмены текущего блока в середине применения, вызывая необратимое повреждение.
После 3 блоков committee (CHAIN_IRREVERSIBLE_SUPPORT_MIN_RUN) LIB продвигается с каждым блоком, сохраняя окно fork DB небольшим.
Гибридное расписание валидаторов
update_witness_schedule() строит гибридное расписание в каждом раунде:
- Слоты, где
signing_keyреального валидатора ненулевой: сохранить реального валидатора. - Пустые или нулевые слоты: заполнить "committee".
Это позволяет реальным валидаторам возвращаться постепенно. Каждый раз, когда реальный валидатор восстанавливает свой ключ подписи через validator_update_operation, следующее перестроение расписания включает его.
Committee исключается из подсчёта версий харфорков и вычисления медианных свойств цепочки (он копирует текущую медиану, поэтому учёт его на каждый слот исказил бы медиану).
Деактивация
Каждое перестроение расписания проверяет условие выхода:
real_witness_slots >= CHAIN_MAX_VALIDATORS × 75%При 21 валидаторе: 21 × 0.75 = 15.75 → 15 слотов реальных валидаторов требуется.
При выполнении условия:
dgp.emergency_consensus_active = false._fork_db.set_emergency_mode(false).- Лог:
"EMERGENCY CONSENSUS MODE deactivated at block #N. R real validators active.".
Сеть возобновляет нормальную работу на следующем цикле расписания.
Восстановление при запуске
database::open() проверяет расписание валидаторов после воспроизведения. Если какой-либо слот пустой (строка ""), экстренный режим был активен при остановке узла:
- Если
emergency_consensus_activeещё не установлен → установить и установить флаг экстренного режима fork DB. - Заполнить все слоты "committee".
- Лог:
"schedule repaired, all N slots set to committee".
Это гарантирует, что расписание всегда согласовано после некорректного завершения во время экстренного режима.
Интеграция с Validator Guard
Плагин validator_guard продолжает работу во время экстренного режима и фактически становится более критичным:
- Реальные валидаторы отключаются (ключ подписи обнуляется) при активации.
- Защита валидатора автоматически транслирует
validator_update_operationдля восстановления ключа подписи каждого валидатора, как только на цепочке обнаруживается нулевой ключ. - Защита
enable-stale-productionв validator guard не блокирует восстановление ключей в экстренном режиме ("Экстренный консенсус обрабатывает собственное восстановление и восстановление ключей может по-прежнему требоваться"). - Как только 15 валидаторов восстанавливают свои ключи, срабатывает условие выхода.
См. Защита валидатора.
Защиты P2P-взаимодействия
Несколько P2P-защитных механизмов знают об экстренном режиме:
| Механизм | Поведение в экстренном режиме |
|---|---|
resync_from_lib() | Полностью пропускается — извлечение блоков вблизи LIB в экстренном режиме приведёт к сбою |
check_wedge_watchdog() | Отдельной ветки под экстренный режим нет. Взводится только когда голова заморожена далеко ниже подтверждённой вершины сети (вторая по высоте голова среди установленных пиров), любое продвижение головы сбрасывает таймер. Только лог, если не выставлен auto-resync-on-wedge = true — тогда подтверждённый клин завершает ноду |
handle_block() (DLT, режим синхронизации, разрыв 0–2) | Обрабатывается как нормальный (не синхронизация) для предотвращения нарушения цикла производства |
Обнаружение зависшей синхронизации снимков (enable-stalled-sync-detection, по умолчанию выключено) | Именно здесь и живёт разделение мастер/последователь. По таймауту читается emergency_consensus_active; если мы — экстренный мастер (держим экстренный ключ и комитет в расписании), соло-производство блоков нормально, и восстановление пропускается. Застрявшему последователю восстановление разрешено |
Защита resync_from_lib() наиболее критична: в экстренном режиме LIB близок к HEAD. Извлечение блоков до LIB и сброс fork DB привели бы к тому, что блоки пиров из реальной сети связываются с повторно заполненным LIB, запуская переключение форка, извлечение ниже зафиксированного LIB и либо сбой, либо повреждение состояния.
Валидация блоков — ослабленное соответствие слот-валидатор
verify_signing_witness() обычно утверждает, что производитель блока точно совпадает с запланированным валидатором. В экстренном режиме:
Если block.validator != scheduled_witness:
dlog("Emergency mode: accepting block from BW at slot scheduled for SW")
→ принять в любом случае (подпись по-прежнему проверяется по signing_key block.validator)Это позволяет экстренным мастерам производить блоки, даже если несколько слотов в ожидающем расписании всё ещё назначены реальным валидаторам.
Совместимость со снимками
Поля экстренного состояния включены в снимки с совместимыми по умолчанию значениями:
emergency_consensus_activeотсутствует в снимке → по умолчаниюfalse.emergency_consensus_start_blockотсутствует → по умолчанию0.
Снимки, созданные во время активного экстренного режима, корректно сохраняют состояние; снимки, созданные до HF12, импортируются как неэкстренные.
Сводка защитных механизмов
| № | Расположение | Механизм |
|---|---|---|
| 1 | update_global_dynamic_data | Активировать только при HF12 + не активен + LIB-блок доступен |
| 2 | update_witness_schedule | Гибридное переопределение + проверка выхода при ≥75% реальных валидаторов |
| 3 | update_last_irreversible_block | Ограничить LIB значением HEAD − 1 в экстренном режиме |
| 4 | verify_signing_witness | Ослабить соответствие слот-валидатор |
| 5 | fork_db._push_block | Детерминированное разрешение хэш-ничьих |
| 6 | maybe_produce_block (мастер) | Обходить синхронизацию, устаревание, участие; пропускать minority fork |
| 7 | maybe_produce_block (последователь) | Сначала синхронизироваться; 21-блочная проверка изоляции |
| 8 | resync_from_lib | Полностью пропускать в экстренном режиме |
| 9 | check_wedge_watchdog | Ветки под экстренный режим нет; от ложного взвода защищают подтверждение вершины и сброс при продвижении головы |
| 10 | handle_block | Почти догнавшие блоки обрабатываются как нормальные в DLT-экстренном режиме |
| 11 | database::open | Исправление расписания при запуске |
| 12 | validator_guard | Не подавлять восстановление ключей в экстренном режиме |
| 13 | snapshot import | Совместимая обработка полей |
| 14 | update_witness_schedule | Исключать committee из подсчёта версий харфорков |
| 15 | update_median_witness_props | Исключать committee из вычисления медианы |
Ключевые инварианты
- Детерминированная активация — использует только временны́е метки в блоках; идентична на каждом узле и при каждом воспроизведении.
- Безопасность DLT-снимка — пропускает активацию если LIB-блок отсутствует в block log.
- Неизменность экстренного форка —
resync_from_lib()отказывается выполняться в экстренном режиме. - Различие мастер/последователь — только узлы с
--emergency-private-keyявляются мастерами. - Сходимость fork DB — детерминированное разрешение хэш-ничьих гарантирует, что все узлы выбирают один блок.
- Безопасность LIB — ограничен значением HEAD − 1 для сохранения защиты отмены.
- Нейтральное голосование committee — committee голосует за текущую применённую версию харфорка, копирует медианные props.
См. также: Fair-DPOS, Разрешение форков, Узел-валидатор, Защита валидатора.