Когда людям продают «high availability для валидатора», они представляют упавшую ноду и второй сервер, который подхватывает за секунды. Картинка приятная и опасная. Для валидатора самый страшный сценарий не «нода упала», а «две ноды одновременно считают себя активным подписантом». Первая подписывает блок, вторая подписывает тот же слот своим ключом, протокол видит две подписи одним валидатором и режет стейк. Это double-sign, и обычный failover к нему не просто не защищает, а ведёт прямой дорогой.
Мы держим валидаторскую инфраструктуру за клиентов, и эту задачу решаем на каждом контракте. Дальше разбираем, почему наивное переключение слешит, и как выглядит failover, который не может подписать дважды.
Split-brain: почему наивный failover убивает стейк
Классический HA-сетап выглядит так: primary-нода подписывает, standby стоит рядом и мониторит primary, при пропаже heartbeat'а standby берёт ключ и начинает подписывать сам. Работает ровно до первого сетевого разрыва между ними.
Сеть разорвалась, но primary жив: он продолжает подписывать, потому что со своей стороны он в порядке. Standby heartbeat'а не видит, решает, что primary умер, и тоже начинает подписывать. Теперь две ноды подписывают одни и те же слоты разными состояниями. Это split-brain, и для валидатора это не деградация сервиса, а гарантированный slashing-инцидент.
Корень проблемы в том, что standby принимает решение на основе того, что он не видит. «Я не получаю heartbeat» и «primary мёртв» это не одно и то же. Разрыв линка между двумя нодами выглядит для каждой из них одинаково: вторая сторона замолчала. Пока standby не может отличить «primary умер» от «я потерял связь с живым primary», любой автоматический promote это рулетка со стейком клиента на кону.
STONITH: пока старый не мёртв, новый не подписывает
Единственный надёжный способ разорвать эту неопределённость это не угадывать состояние primary, а принудительно его обнулять перед промоушеном. Дисциплина называется STONITH: shoot the other node in the head. Прежде чем standby возьмёт ключ, primary должен быть гарантированно мёртв, а не предположительно.
«Гарантированно» означает внешнее действие, которое не зависит от самочувствия primary:
- Power fencing. Дёрнуть питание primary через управляемый PDU или IPMI, дождаться подтверждения, что порт обесточен, и только потом промоутить standby.
- Network fencing. Закрыть primary порт на свитче, физически отрезав его от сети подписи, и получить подтверждение со свитча.
Ключевое слово это подтверждение. Standby не промоутится «через 5 секунд после пропажи heartbeat'а». Он промоутится после того, как fencing-контур вернул «primary обесточен» или «порт primary закрыт». Нет подтверждения, нет промоушена: лучше пропустить несколько attestation-слотов и потерять копейки на inactivity, чем подписать дважды и потерять стейк.
vLAN-fencing и witness вместо гадания
Power fencing хорош, когда у вас есть управляемый доступ к питанию. В облаке и в части коло его нет, и тогда основным примитивом становится network fencing на уровне vLAN плюс внешний witness.
Witness это третья точка, которая не участвует в подписи и стоит в отдельной сети. Решение о промоушене принимает не standby в одиночку, а кворум: standby плюс witness. Если standby потерял связь с primary, но witness primary всё ещё видит, промоушена нет, потому что primary жив. Промоушен случается, только когда и standby, и witness согласны, что primary недостижим, и при этом сработал fencing на свитче. Третий голос убирает ту самую ситуацию, где одна нода судит о жизни другой по тишине в линке.
Это дороже и медленнее наивного heartbeat-failover. Это и есть цена за то, чтобы переключение не стоило клиенту стейка.
Чего мы не делаем
Половина дисциплины это запрет на удобные, но опасные паттерны:
- Автоматический promote по таймеру. Никаких «heartbeat пропал на 10 секунд, промоутим». Промоушен только после подтверждённого fencing.
- Один и тот же ключ на двух горячих нодах без fencing. Горячий standby с тем же ключом и автоматикой promote это заряженный double-sign, ждущий сетевого разрыва.
- Failover-логика внутри той же сети, что и подпись. Контур принятия решения о промоушене и witness живут в отдельной сети, иначе один сетевой инцидент валит и подпись, и арбитраж одновременно.
Эти три правила скучные и спасают стейк чаще, чем любая оптимизация latency.
Как это выглядит у клиента
На своём полигоне мы отрабатываем fencing-сценарии до того, как ставим их на клиентский валидатор: режем линки, гасим primary в момент подписи, проверяем, что standby не промоутится без подтверждения. На клиентском контракте это превращается в раннбук: какой PDU или свитч дёргаем, где стоит witness, сколько слотов мы готовы пропустить, прежде чем эскалируем дежурному.
Если у вас slashing-чувствительный валидатор и вы хотите failover, который скорее пропустит несколько слотов, чем подпишет дважды, это часть того, что мы держим в web3-операциях и закрываем через operate. Хотите разобрать ваш текущий HA-сетап на предмет split-brain: напишите нам.