Справочник оператора валидатора
Как поднять валидатор VIZ на чистом VPS с Debian и как потом его обслуживать.
Здесь описана вся процедура целиком. Справочные материалы не пересказываются, а даются ссылками: Конфигурация — все параметры, Узел валидатора — цикл производства блоков и сообщения в логе, Сборка — сборка из исходников.
Часть 0 — Перед началом
Два профиля узла
| Профиль | Подписывает блоки | Доступ извне | Назначение |
|---|---|---|---|
| Валидатор | да | нет: RPC на loopback, p2p только исходящий | производство блоков |
| Релей (сид-узел) | нет | входящий :2001, при желании публичный RPC | обслуживание сети и надёжный пир для собственных валидаторов |
Не совмещайте их. На валидаторе лежит ключ подписи, и публичный p2p ничего не даёт взамен потерянной изоляции этого ключа. Профиль релея описан в части 6.
Что понадобится
- VPS с Debian 12 или 13 и доступом root либо sudo.
- Зарегистрированный аккаунт VIZ. Как его получить и какая доля нужна работоспособному валидатору, здесь не рассматривается: см. Стейкинг и DAO.
- Домен не нужен. Валидатору не требуется входящий DNS.
Сколько ресурсов нужно
Валидатору не нужна мощная машина: он не хранит историю, не отдаёт API и не держит архив. Конфигурация ниже ограничивает его объём целиком.
| Валидатор | Релей без ключа | |
|---|---|---|
| vCPU | 2 | 2 (4, если принимаете много входящих пиров) |
| RAM | 4 ГБ + 2 ГБ swap | 4 ГБ + 2 ГБ swap |
| Диск | 20 ГБ SSD | 20 ГБ SSD (40 ГБ, если отдаёте снимки) |
Из чего складывается объём на диске — эти цифры можно проверить самому:
| Размер | Чем ограничен | |
|---|---|---|
shared_memory.bin | 2 ГБ, растёт шагами по 2 ГБ | shared-file-size / inc-shared-file-size |
| Скользящий лог блоков DLT | около 3,5 суток блоков | dlt-block-log-max-blocks = 100000 |
| Локальные снимки | 2 файла, каждый меньше 2 ГБ | Количество задают snapshot-every-n-blocks и snapshot-max-age-days. 2 ГБ — это предел передачи по P2P, то есть верхняя граница: реальный снимок основной сети меньше. Посмотрите свой через du -sh, прежде чем считать по худшему случаю. |
| Логи Docker | 30 МБ | max-size × max-file в compose.yml |
Остальное занимают Debian и образ vizd. На валидаторе нет ничего, что росло бы без заданной вами границы, поэтому 20 ГБ — рабочая цифра, а не надежда. Одно исключение описано в части 2: неверно выбранная частота снимков этот расчёт ломает.
Не экономьте на памяти. Подпись должна уложиться в трёхсекундный слот, и узел, который в этот момент уходит в swap, слот пропускает и пишет в лог lag. Два гигабайта swap — запас на короткий скачок при импорте снимка, а не замена оперативной памяти.
Swap нужен обоим профилям: релей импортирует снимки так же и получает такой же скачок. Разница только в последствиях. Релей при выходе в swap замедляется, валидатор теряет блок.
Нагрузка на процессор у релея зависит от числа принятых входящих пиров, а не от публичности самой по себе: на :2001 он публичен по определению. Ограничьте число соединений через p2p-max-connections и считайте от него. Значение по умолчанию щедрое, а релею, который обслуживает ваши же валидаторы, незачем тянуть 200 чужих подключений.
Публичный RPC — отдельная машина с другой экономикой, потому что плагины истории хранят всё. Требования для этого профиля есть в таблице в разделе Docker; на валидатор его ставить не нужно.
Порты
| Порт | Валидатор | Релей | Назначение |
|---|---|---|---|
| 2001 | только исходящий | входящий | p2p |
| 8090 | только loopback | при желании публичный | HTTP JSON-RPC |
| 8092 | только исходящий | при желании входящий | передача снимков |
Часть 1 — Подготовка сервера
Все команды выполняются от root на чистой машине, по порядку. В конце — три проверки, которые нужно пройти до развёртывания узла.
Пользователь с sudo и ключом SSH
На рабочей станции, если ключа ещё нет:
ssh-keygen -t ed25519 -C "viz-validator"На сервере, от root:
adduser --gecos "" viz
usermod -aG sudo viz
install -d -m 700 -o viz -g viz /home/viz/.sshЗатем скопируйте публичный ключ с рабочей станции:
ssh-copy-id -i ~/.ssh/id_ed25519.pub viz@YOUR_SERVER_IPПрежде чем идти дальше, убедитесь, что вход работает: ssh viz@YOUR_SERVER_IP должен пройти без запроса пароля.
Ужесточение настроек SSH
sudo tee /etc/ssh/sshd_config.d/10-hardening.conf >/dev/null <<'EOF'
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
EOF
sudo sshd -t && sudo systemctl reload sshНе закрывайте текущую сессию
Оставьте этот shell открытым. Откройте второй терминал, выполните ssh viz@YOUR_SERVER_IP и убедитесь, что вход проходит. Только после этого закрывайте первый. Если аутентификация по ключу окажется сломанной, а сессия уже закрыта, доступ к серверу останется только через аварийную консоль хостера.
Файрвол
Входящие соединения запрещаем по умолчанию. Валидатору не нужен ни один входящий порт, кроме SSH: p2p работает только исходящими соединениями, а RPC Docker публикует на loopback.
sudo apt update && sudo apt install -y ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw --force enableОператорам релея нужно добавить sudo ufw allow 2001/tcp, см. часть 6. На валидаторе этого делать не нужно.
Синхронизация времени
Этот шаг обязателен. Блоки производятся по расписанию слотов с интервалом 3 секунды. Если часы уходят хотя бы на заметную долю слота, узел подписывает блоки, которые сеть не принимает, либо вовсе не успевает в свой слот. Выглядит это в точности как проблема с сетью.
sudo apt install -y systemd-timesyncd
sudo timedatectl set-ntp true
timedatectl statusУ узла есть и собственная проверка NTP: при слишком большом расхождении он отказывается производить блоки, см. раздел про NTP в Узле валидатора. Синхронизация на хосте и проверка в узле друг друга не заменяют.
Swap
Двух гигабайт достаточно. Swap здесь нужен как запас на короткий скачок при импорте снимка, а не как место для постоянной работы: валидатор, который живёт в swap, теряет слоты.
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstabЗаодно зафиксируйте низкий swappiness, чтобы ядро охотнее освобождало страничный кэш, чем выгружало активное состояние цепи:
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-vizd.conf
sudo sysctl --systemDocker
Ставим из официального репозитория Docker: пакет docker.io из Debian устарел и не содержит плагина compose.
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg \
-o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
| sudo tee /etc/apt/sources.list.d/docker.list >/dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker vizЧтобы смена группы вступила в силу, выйдите из системы и войдите снова.
Дальше не идите, пока не пройдут все три команды:
timedatectl | grep 'System clock synchronized: yes'
docker run --rm hello-world
sudo ufw status | grep 'Status: active'Часть 2 — Развёртывание узла
Ключ подписи
docker run --rm -it vizblockchain/vizd:latest cli_wallet --suggest-brain-keyЗапишите приватный ключ в формате WIF и публичный ключ VIZ.... Приватный пойдёт в config.ini, публичный регистрируется в блокчейне в части 3.
Активный ключ аккаунта на этой машине не появляется никогда. Он остаётся в браузере и нужен только для переключения в части 3. Ключ подписи — отдельная и легко заменяемая сущность: при утечке его достаточно заменить (часть 5), больше ничего не теряется.
Что где лежит
sudo install -d -o viz -g viz /opt/vizd/logs
cd /opt/vizdЗдесь лежат три вещи: compose.yml, config.ini и logs/. Состояние цепи — нет: оно хранится в именованном томе Docker, и на этом построена таблица решений из части 5.
compose.yml
name: viz-validator
services:
vizd:
image: vizblockchain/vizd:latest
container_name: viz-validator
restart: unless-stopped
ports:
- "127.0.0.1:8090:8090" # только loopback: для проверок, наружу не публикуется
volumes:
- vizd-state:/var/lib/vizd
- ./config.ini:/etc/vizd/config.ini:ro
- ./logs:/var/log/vizd
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
volumes:
vizd-state:В этом файле важны три момента:
- Монтирование конфига в режиме
:roсделано правильно. Стартовый скрипт копирует/etc/vizd/config.iniв каталог данных, так что в смонтированный файл контейнер не пишет. - Состояние цепи лежит в именованном томе
vizd-state.docker compose downего сохраняет,docker compose down -vудаляет. Один этот флаг отделяет обычный перезапуск от полной загрузки состояния заново. - Логи идут в драйвер Docker
json-fileс ограничением по размеру, поэтому диск они не заполнят. Читать их нужно черезdocker compose logs. Монтирование./logsоставлено на случай, если вы добавите файловый appender: при настройках ниже, где вывод идёт только в stderr, каталог остаётся пустым.
config.ini
# ─── p2p (только исходящие; наружу ничего не слушает) ────────────────
p2p-seed-node = seed3.viz.world:2001
p2p-seed-node = seed1.viz.world:2001
p2p-seed-node = rpc.viz.cx:2001
# ─── RPC: compose публикует его на loopback для проверок ─────────────
webserver-http-endpoint = 0.0.0.0:8090
webserver-thread-pool-size = 1
single-write-thread = true
enable-plugins-on-push-transaction = false
# ─── разделяемая память ──────────────────────────────────────────────
shared-file-size = 2G
min-free-shared-file-size = 500M
inc-shared-file-size = 2G
# ─── плагины: только производство блоков и RPC для проверок ──────────
plugin = chain p2p json_rpc webserver database_api validator validator_api
plugin = snapshot
# ─── идентификатор валидатора ────────────────────────────────────────
validator = "YOUR_ACCOUNT"
private-key = YOUR_SIGNING_WIF
# ─── снимки: берём у доверенного пира, сами не отдаём ────────────────
snapshot-dir = /var/lib/vizd/snapshots
sync-snapshot-from-trusted-peer = true
trusted-snapshot-peer = seed3.viz.world:8092
trusted-snapshot-peer = seed1.viz.world:8092
trusted-snapshot-peer = seed2.viz.world:8092
trusted-snapshot-peer = rpc.viz.cx:8092
allow-snapshot-serving = false
snapshot-every-n-blocks = 28800
snapshot-max-age-days = 2
dlt-block-log-max-blocks = 100000
# ─── безопасность производства блоков ────────────────────────────────
required-participation = 33
enable-stale-production = false
skip-virtual-ops = true
clear-votes-before-block = 0
# ─── логи ────────────────────────────────────────────────────────────
# fc строг к этим секциям: внутри [log.*] и [logger.*] нельзя писать
# комментарии в конце строки, а level принимает только значения
# all/debug/info/warn/error/off. Значение "none" недопустимо, узел молча
# вернётся к обычному выводу в stderr.
[log.console_appender.stderr]
stream=std_error
[logger.default]
level=info
appenders=stderrЗаполнить нужно два места:
validator = "YOUR_ACCOUNT"— имя аккаунта, в кавычках.private-key = YOUR_SIGNING_WIF— WIF из созданной выше пары, без кавычек.
Параметр называется validator. Старое имя witness пока работает, но узел пишет в лог Config option 'witness' is deprecated, use 'validator' instead. Если вы видите это предупреждение, значит скопировали устаревший пример.
seed2.viz.world есть в списке снимков, но его нет в списке p2p. Асимметрия сделана намеренно: порт :8092 у него отдаёт снимки, а :2001 подключений не принимает. Исправлять это не нужно.
Снимки удаляются только по возрасту, ограничения «хранить N штук» нет
Число снимков на диске равно частоте, умноженной на срок хранения, и больше ничем не ограничено. При snapshot-every-n-blocks = 28800 снимок создаётся примерно раз в сутки (28800 × 3 с), поэтому с snapshot-max-age-days = 2 на диске лежат около двух файлов. Если снизить частоту до 1200, получится порядка 240 файлов размером до 2 ГБ: любой диск из таблицы в начале справочника заполнится задолго до того, как сработает ограничение по возрасту.
Глубокая история снимков валидатору не нужна. Двух файлов хватает для быстрого локального перезапуска, а всё, что хуже, — это загрузка состояния у доверенного пира, и она занимает минуты (см. «Потеря машины» в части 5).
Запуск
В файле лежит ключ подписи, поэтому закройте к нему доступ до первого запуска узла:
chmod 600 config.ini
docker compose up -d
docker compose logs -fПри первом запуске, когда состояния ещё нет, в логе появится:
Node has no state. Triggering P2P snapshot sync from trusted peers...Загрузка состояния из снимка занимает минуты, а не часы. Если вместо этого узел проигрывает лог блоков с генезиса, значит до доверенного пира со снимками он не дошёл: проверьте список trusted-snapshot-peer и исходящий доступ к :8092.
Проверка:
# Номер головного блока должен вырасти между двумя запросами с интервалом ~7 с
curl -s http://127.0.0.1:8090 -H 'Content-Type: application/json' \
--data '{"id":1,"jsonrpc":"2.0","method":"call","params":["database_api","get_dynamic_global_properties",[]]}' \
| grep -o '"head_block_number":[0-9]*'
# Наружу ничего слушать не должно
ss -tlnp | grep 8090 # в выводе только 127.0.0.1:8090, и ничего большеЧасть 3 — Переключение в блокчейне
Стоп
Не начинайте эту часть, пока не пройдены проверки из части 2 и узел не догнал сеть. Если зарегистрировать ключ подписи на узле, который ещё синхронизируется, планировщик выдаст слоты, которые заполнить не получится.
1. Задайте ключ подписи
Откройте https://wallet.viz.world/dao/witness-params/ и войдите активным ключом аккаунта прямо в браузере. В качестве ключа подписи укажите публичный ключ VIZ..., созданный в части 2.
Сначала ключ, потом голоса
Валидатор без ключа сообщает running_version 0.0.0 и не отображается в списке валидаторов DAO в веб-кошельке, то есть проголосовать за него невозможно. Видимым его делает именно установленный ключ подписи. Если сделать наоборот, будет казаться, что кошелёк неисправен.
2. Соберите голоса
Валидатор без голосов зарегистрирован, но в расписание не попадает никогда: его virtual_scheduled_time равен максимальному uint, и планировщик до него не доходит. Узел будет сколько угодно долго стоять на текущем блоке — исправный, простаивающий и ничего не производящий.
Голоса подаются операцией account_validator_vote, а вес голоса определяется вестингом голосующего, поэтому несколько крупных держателей перевешивают множество мелких. См. Стейкинг и DAO.
3. Убедитесь, что блоки производятся
curl -s http://127.0.0.1:8090 -H 'Content-Type: application/json' \
--data '{"id":1,"jsonrpc":"2.0","method":"call","params":["validator_api","get_validator_by_account",["YOUR_ACCOUNT"]]}'Сравните signing_key со своим публичным ключом и проверьте, что last_confirmed_block_num растёт между запросами.
Валидатор с голосами должен работать без простоев
Если пропустить достаточно назначенных блоков подряд, VIZ отключит валидатора сам, обнулив его ключ. Чтобы вернуться, придётся заново зарегистрировать ключ и снова занять место в расписании. Плановое обслуживание работающего валидатора должно укладываться в короткий перезапуск.
total_missed считает за всё время
Этот счётчик не сбрасывается. Запишите текущее значение сейчас и настройте оповещение на его прирост. Абсолютное число не говорит ни о чём, кроме того, что узел когда-то работал.
Чтобы отключить валидатора или откатить изменения, задайте ему нулевой ключ подписи:
VIZ1111111111111111111111111111111114T1AnmТем же способом цепь отключает валидатора автоматически, так что это штатная остановка, а не обходной приём.
TLS в cli_wallet
По состоянию на 27.07.2026 cli_wallet в образе vizd не мог завершить ни одно рукопожатие wss, отказывался работать по обычному http и понимал только обычный ws, который публичные узлы не открывают. Если столкнётесь с этим, отправляйте транзакции через веб-кошелёк или клиентскую библиотеку. Прежде чем считать, что проблема сохранилась, проверьте заново.
Проверка: signing_key совпадает с вашим публичным ключом, а last_confirmed_block_num растёт между двумя запросами с интервалом в минуту.
Часть 4 — Контроль состояния
Незаметно сломаться могут три вещи: контейнер падает, головной блок перестаёт расти, узел успевает за сетью, но пропускает собственные слоты. Одна проверка закрывает все три и срабатывает только при сбое. Монитор, который пишет и при успехе, рано или поздно заглушают, а заглушённый монитор бесполезен.
#!/usr/bin/env bash
# /opt/vizd/health.sh — запускается по cron каждые 15 минут.
# Проверяет, что контейнер работает, головной блок растёт, а число
# пропущенных блоков не увеличивается.
# Пишет только при сбое, при успехе молчит.
set -euo pipefail
DIR=/opt/vizd
RPC=http://127.0.0.1:8090
STATE="$DIR/health.state"
source "$DIR/.env" # ALERT_TOKEN, ALERT_CHAT, WITNESS_ACCOUNT
alert() {
curl -s --max-time 10 "https://api.telegram.org/bot${ALERT_TOKEN}/sendMessage" \
-d chat_id="${ALERT_CHAT}" \
--data-urlencode text="viz-validator ($(hostname)): $1" >/dev/null || true
}
if ! docker ps --format '{{.Names}}' | grep -q '^viz-validator$'; then
alert "container not running"; exit 1
fi
rpc() { curl -s --max-time 10 "$RPC" -H 'Content-Type: application/json' --data "$1"; }
DGP='{"id":1,"jsonrpc":"2.0","method":"call","params":["database_api","get_dynamic_global_properties",[]]}'
head1=$(rpc "$DGP" | grep -o '"head_block_number":[0-9]*' | grep -o '[0-9]*$' || true)
sleep 9 # три интервала блока
head2=$(rpc "$DGP" | grep -o '"head_block_number":[0-9]*' | grep -o '[0-9]*$' || true)
if [ -z "$head1" ] || [ -z "$head2" ]; then
alert "RPC not answering on 127.0.0.1:8090"; exit 1
fi
if [ "$head2" -le "$head1" ]; then
alert "head frozen at #$head2 (was #$head1 nine seconds earlier)"; exit 1
fi
missed=$(rpc "{\"id\":2,\"jsonrpc\":\"2.0\",\"method\":\"call\",\"params\":[\"validator_api\",\"get_validator_by_account\",[\"$WITNESS_ACCOUNT\"]]}" \
| grep -o '"total_missed":[0-9]*' | grep -o '[0-9]*$' || true)
if [ -n "$missed" ]; then
prev=$(cat "$STATE" 2>/dev/null || echo "$missed")
[ "$missed" -gt "$prev" ] && alert "missed blocks growing: $prev -> $missed"
echo "$missed" > "$STATE"
fiРазбор ответов на curl и grep выбран сознательно. Без jq на минимальной машине не нужно ставить ничего дополнительно, и у проверки нет зависимости, которая со временем разъедется.
Учётные данные лежат рядом, в файле .env с правами 600:
cat > /opt/vizd/.env <<'EOF'
ALERT_TOKEN=123456:your-telegram-bot-token
ALERT_CHAT=your-telegram-chat-id
WITNESS_ACCOUNT=your-account
EOF
chmod 600 /opt/vizd/.env
chmod 700 /opt/vizd/health.shИ запись в cron (crontab -e от пользователя viz):
*/15 * * * * /opt/vizd/health.sh >> /opt/vizd/health.log 2>&1Что узел пишет в лог при каждой попытке произвести блок — произведён, пропущен, минорный форк, watchdog — собрано в таблицу в разделе Узел валидатора, поэтому здесь не повторяется. Про метрики и более тонкие оповещения см. Мониторинг.
Проверьте оповещение, а не предполагайте, что оно работает:
docker compose stop vizd # затем дождитесь следующего запуска по cron
# оповещение должно прийти в течение 15 минут
docker compose start vizdОповещение, которое ни разу не проверяли, оповещением не является. Сделайте это один раз сейчас, пока пара пропущенных слотов ничего не стоит, а не выясняйте во время аварии, что токен указан неверно.
Часть 5 — Повседневная эксплуатация
Начните с этой таблицы: именно здесь операторы чаще всего выбирают не то, и неверный выбор стоит слотов, которые при обычном перезапуске остались бы целы.
| Ситуация | Команда | Цена |
|---|---|---|
| изменили только конфиг | docker compose restart vizd | около одного слота |
| новый образ | сначала docker compose pull, потом docker compose up -d | догон пропущенных блоков, простой почти нулевой |
| узел застрял или ушёл на плохой форк | docker compose down -v && docker compose up -d | состояние загружается заново, слоты теряются до конца синхронизации |
Обновление образа
vizblockchain/vizd:latest пересобирается при каждом push в master, поэтому версию поднимать негде: сам тег перемещается.
Ловушка: docker compose up -d не скачивает новый :latest, если тег уже есть локально. Он видит совпадение по тегу, берёт локальный образ и сообщает об успехе. Сначала нужен docker compose pull, иначе вы перезапустите тот же самый бинарник и решите, что обновление ничего не изменило.
cd /opt/vizd
docker compose pull
docker compose up -d
docker compose logs -fОбновляйте по одной машине. Между машинами проверяйте, что головной блок растёт, а total_missed не увеличился. Если в новом образе окажется регрессия, предыдущий останется на диске: docker images vizblockchain/vizd покажет его id, и этот id можно закрепить в compose.yml, чтобы откатиться.
Пересоздание контейнера не выводит узел из застрявшего состояния
Состояние цепи лежит в томе vizd-state, и compose подключает этот том заново при up -d, restart и даже при --force-recreate. Новый контейнер читает то же испорченное состояние и застревает снова. Выглядит это так, будто перезапуск не помог, хотя на самом деле ничего и не сбрасывалось.
Том удаляет только docker compose down -v. В этом и вся разница, поэтому флаг стоит помнить наизусть, а не искать в документации во время аварии.
Выход с мёртвого форка и порядок действий
Признаки: головной блок не растёт, хотя пиры подключены, либо канонический пир пишет, что мягко забанил вас за мёртвый форк.
Канонический источник снимков нужно настроить до удаления состояния. Если сначала выполнить down -v, узел начнёт синхронизацию с теми пирами, которые у него есть, а на мёртвом форке это могут быть те же пиры, которые этот форк и подсунули. Состояние загрузится заново, а результат окажется тем же. Правильный порядок:
- Убедитесь, что в
config.iniсписокtrusted-snapshot-peerначинается сseed3.viz.world:8092, аsync-snapshot-from-trusted-peer = true. - Убедитесь, что список
p2p-seed-nodeначинается сseed3.viz.world:2001: пиры перебираются по порядку, поэтому канонический узел должен стоять первым. docker compose down -v && docker compose up -d.- Дождитесь строки
Node has no state. Triggering P2P snapshot sync from trusted peers...и проверьте, что головной блок оказался близко к текущему блоку сети, а не на прежней высоте.
Узел отказался принять состояние после обновления
Если при старте узел завершает работу и пишет, что состояние собрано другим компилятором, другой сборкой или другой версией Boost, это не повреждение данных, а намеренный отказ. См. Переход на Boost 1.9x.
Замена ключа
- Создайте новую пару ключей так же, как в части 2.
- Замените
private-keyвconfig.ini. docker compose restart vizd.- Зарегистрируйте новый публичный ключ в блокчейне так же, как в части 3.
Регистрация в блокчейне и есть точка переключения. Пока она не прошла, действительные блоки подписывает старый ключ; как только прошла, действителен только новый. Периода, когда работают оба, не существует, поэтому двойного производства здесь быть не может. Но между шагами 3 и 4 остаётся промежуток, когда узел держит ключ, который цепь ещё не приняла, и пишет no_private_key. Этот промежуток стоит сократить.
Потеря машины
Восстанавливать нечего. Валидатора определяют ключ подписи и его регистрация в блокчейне, и то и другое хранится вне машины: ключ — в менеджере паролей, регистрация — в цепи. Повторите части 1 и 2 на новом сервере, и узел за несколько минут поднимет состояние из снимка. Состояние цепи резервировать не нужно, по сравнению с тем, что отдаст сеть, оно ничего не стоит.
Несколько валидаторов
На одну машину — один ключ подписи. Один и тот же WIF на двух машинах использовать нельзя: это двойное производство, когда два узла подписывают разные блоки для одного слота, и именно за это сеть наказывает. Разные аккаунты с разными ключами полностью независимы, так делать можно.
Часть 6 — Релей и сид-узел без ключа
Публичных сид-узлов в сети VIZ мало. Релей не добавляет рисков, связанных с ключом подписи, и даёт вашим валидаторам надёжного пира на инфраструктуре, которую вы контролируете, при том что сами валидаторы остаются закрытыми для входящих соединений. Если у вас есть валидатор, релей — самая полезная вторая машина, которую можно к нему добавить.
Релей разворачивается так же, как в части 2, с несколькими отличиями:
- Параметров
validatorиprivate-keyнет. Он ничего не подписывает, и защищать на нём нечего. p2p-endpoint = 0.0.0.0:2001плюсsudo ufw allow 2001/tcp. Это единственный случай, когда входящий p2p уместен.allow-snapshot-serving = true, если хотите отдавать снимки другим операторам; вместе с этим понадобитсяsudo ufw allow 8092/tcp. Это единственное изменение, которое влияет на объём диска: снимки теперь хранятся для чужих загрузок, поэтому рассчитывайте на 40 ГБ и следите заsnapshot-max-age-days.network_broadcast_apiи плагины истории добавляйте только если обслуживаете клиентов API. Они расходуют память и диск, и именно из-за них машина на 20 ГБ превращается в машину на 50 ГБ и больше. Чистому релею они не нужны.- Публичный RPC, если он вообще нужен, ставьте за обратный прокси с TLS. Порт
:8090напрямую наружу не отдавайте.
Не размещайте релей на той же машине, где стоит единственный валидатор
Тогда отказ одной машины уносит и производителя блоков, и пира, от которого он зависит. Релей, обслуживавший валидатор на том же хосте, уже был причастен к аварии с форком. Если нужны оба, разнесите их по разным машинам.
Часть 7 — Таблица симптомов
Здесь собраны симптомы уровня сервера и развёртывания. То, что видно в логе производства блоков — no_private_key, low_participation, minority_fork и подобные, — сведено в таблицу в разделе Узел валидатора.
| Симптом | Первая команда | Обычная причина |
|---|---|---|
| Контейнер постоянно перезапускается | docker compose logs --tail=100 vizd | ошибка в config.ini: комментарий в конце строки внутри секции [log.*] либо недопустимый уровень логирования |
| RPC не отвечает на loopback | docker compose ps, затем ss -tlnp | grep 8090 | контейнер не запущен либо в ports: пропущен префикс 127.0.0.1: |
| Головной блок не растёт, пиры подключены | docker compose logs --tail=200 vizd | grep -i fork | узел застрял или ушёл на мёртвый форк (часть 5); обычный перезапуск это не лечит |
| Головной блок не растёт, пиров нет | docker compose logs vizd | grep -i 'p2p|peer' | ни один p2p-seed-node недоступен либо закрыт исходящий :2001; проверьте через nc -z seed3.viz.world 2001 |
total_missed растёт, головной блок тоже | timedatectl | часы разошлись либо машина не успевает подписать блок в своём слоте: проверьте нагрузку и обращения к swap |
| После правильного на вид переключения блоки не производятся | get_validator_by_account: сравните signing_key, посмотрите virtual_scheduled_time | нет голосов, поэтому слоты не выдаются (часть 3), либо зарегистрированный ключ не совпадает с тем, что в config.ini |
| При старте после обновления узел отказался принять состояние | docker compose logs vizd | head -40 | не совпадает версия состояния по Boost или компилятору: Переход на Boost 1.9x |
| Диск заполнен | df -h && docker system df | старые образы и неиспользуемые тома: docker image prune -a; проверьте срок хранения снимков (snapshot-max-age-days) и наличие logging.options.max-size |