Skip to content

Справочник оператора валидатора

Как поднять валидатор VIZ на чистом VPS с Debian и как потом его обслуживать.

Здесь описана вся процедура целиком. Справочные материалы не пересказываются, а даются ссылками: Конфигурация — все параметры, Узел валидатора — цикл производства блоков и сообщения в логе, Сборка — сборка из исходников.


Часть 0 — Перед началом

Два профиля узла

ПрофильПодписывает блокиДоступ извнеНазначение
Валидаторданет: RPC на loopback, p2p только исходящийпроизводство блоков
Релей (сид-узел)нетвходящий :2001, при желании публичный RPCобслуживание сети и надёжный пир для собственных валидаторов

Не совмещайте их. На валидаторе лежит ключ подписи, и публичный p2p ничего не даёт взамен потерянной изоляции этого ключа. Профиль релея описан в части 6.

Что понадобится

  • VPS с Debian 12 или 13 и доступом root либо sudo.
  • Зарегистрированный аккаунт VIZ. Как его получить и какая доля нужна работоспособному валидатору, здесь не рассматривается: см. Стейкинг и DAO.
  • Домен не нужен. Валидатору не требуется входящий DNS.

Сколько ресурсов нужно

Валидатору не нужна мощная машина: он не хранит историю, не отдаёт API и не держит архив. Конфигурация ниже ограничивает его объём целиком.

ВалидаторРелей без ключа
vCPU22 (4, если принимаете много входящих пиров)
RAM4 ГБ + 2 ГБ swap4 ГБ + 2 ГБ swap
Диск20 ГБ SSD20 ГБ SSD (40 ГБ, если отдаёте снимки)

Из чего складывается объём на диске — эти цифры можно проверить самому:

РазмерЧем ограничен
shared_memory.bin2 ГБ, растёт шагами по 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, прежде чем считать по худшему случаю.
Логи Docker30 МБ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

На рабочей станции, если ключа ещё нет:

bash
ssh-keygen -t ed25519 -C "viz-validator"

На сервере, от root:

bash
adduser --gecos "" viz
usermod -aG sudo viz
install -d -m 700 -o viz -g viz /home/viz/.ssh

Затем скопируйте публичный ключ с рабочей станции:

bash
ssh-copy-id -i ~/.ssh/id_ed25519.pub viz@YOUR_SERVER_IP

Прежде чем идти дальше, убедитесь, что вход работает: ssh viz@YOUR_SERVER_IP должен пройти без запроса пароля.

Ужесточение настроек SSH

bash
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.

bash
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 секунды. Если часы уходят хотя бы на заметную долю слота, узел подписывает блоки, которые сеть не принимает, либо вовсе не успевает в свой слот. Выглядит это в точности как проблема с сетью.

bash
sudo apt install -y systemd-timesyncd
sudo timedatectl set-ntp true
timedatectl status

У узла есть и собственная проверка NTP: при слишком большом расхождении он отказывается производить блоки, см. раздел про NTP в Узле валидатора. Синхронизация на хосте и проверка в узле друг друга не заменяют.

Swap

Двух гигабайт достаточно. Swap здесь нужен как запас на короткий скачок при импорте снимка, а не как место для постоянной работы: валидатор, который живёт в swap, теряет слоты.

bash
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, чтобы ядро охотнее освобождало страничный кэш, чем выгружало активное состояние цепи:

bash
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-vizd.conf
sudo sysctl --system

Docker

Ставим из официального репозитория Docker: пакет docker.io из Debian устарел и не содержит плагина compose.

bash
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

Чтобы смена группы вступила в силу, выйдите из системы и войдите снова.

Дальше не идите, пока не пройдут все три команды:

bash
timedatectl | grep 'System clock synchronized: yes'
docker run --rm hello-world
sudo ufw status | grep 'Status: active'

Часть 2 — Развёртывание узла

Ключ подписи

bash
docker run --rm -it vizblockchain/vizd:latest cli_wallet --suggest-brain-key

Запишите приватный ключ в формате WIF и публичный ключ VIZ.... Приватный пойдёт в config.ini, публичный регистрируется в блокчейне в части 3.

Активный ключ аккаунта на этой машине не появляется никогда. Он остаётся в браузере и нужен только для переключения в части 3. Ключ подписи — отдельная и легко заменяемая сущность: при утечке его достаточно заменить (часть 5), больше ничего не теряется.

Что где лежит

bash
sudo install -d -o viz -g viz /opt/vizd/logs
cd /opt/vizd

Здесь лежат три вещи: compose.yml, config.ini и logs/. Состояние цепи — нет: оно хранится в именованном томе Docker, и на этом построена таблица решений из части 5.

compose.yml

yaml
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

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).

Запуск

В файле лежит ключ подписи, поэтому закройте к нему доступ до первого запуска узла:

bash
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.

Проверка:

bash
# Номер головного блока должен вырасти между двумя запросами с интервалом ~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. Убедитесь, что блоки производятся

bash
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 — Контроль состояния

Незаметно сломаться могут три вещи: контейнер падает, головной блок перестаёт расти, узел успевает за сетью, но пропускает собственные слоты. Одна проверка закрывает все три и срабатывает только при сбое. Монитор, который пишет и при успехе, рано или поздно заглушают, а заглушённый монитор бесполезен.

bash
#!/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:

bash
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):

text
*/15 * * * * /opt/vizd/health.sh >> /opt/vizd/health.log 2>&1

Что узел пишет в лог при каждой попытке произвести блок — произведён, пропущен, минорный форк, watchdog — собрано в таблицу в разделе Узел валидатора, поэтому здесь не повторяется. Про метрики и более тонкие оповещения см. Мониторинг.

Проверьте оповещение, а не предполагайте, что оно работает:

bash
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, иначе вы перезапустите тот же самый бинарник и решите, что обновление ничего не изменило.

bash
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, узел начнёт синхронизацию с теми пирами, которые у него есть, а на мёртвом форке это могут быть те же пиры, которые этот форк и подсунули. Состояние загрузится заново, а результат окажется тем же. Правильный порядок:

  1. Убедитесь, что в config.ini список trusted-snapshot-peer начинается с seed3.viz.world:8092, а sync-snapshot-from-trusted-peer = true.
  2. Убедитесь, что список p2p-seed-node начинается с seed3.viz.world:2001: пиры перебираются по порядку, поэтому канонический узел должен стоять первым.
  3. docker compose down -v && docker compose up -d.
  4. Дождитесь строки Node has no state. Triggering P2P snapshot sync from trusted peers... и проверьте, что головной блок оказался близко к текущему блоку сети, а не на прежней высоте.

Узел отказался принять состояние после обновления

Если при старте узел завершает работу и пишет, что состояние собрано другим компилятором, другой сборкой или другой версией Boost, это не повреждение данных, а намеренный отказ. См. Переход на Boost 1.9x.

Замена ключа

  1. Создайте новую пару ключей так же, как в части 2.
  2. Замените private-key в config.ini.
  3. docker compose restart vizd.
  4. Зарегистрируйте новый публичный ключ в блокчейне так же, как в части 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 не отвечает на loopbackdocker 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