ZYCORD docs
Русский
ZycordДокументацияЗапуск узла

Запуск узла

Руководство оператора по zycordd. Протокол, на котором он говорит, — это спецификация сетевого протокола; эта страница — о его эксплуатации.

Версия в одну строку#

zycordd --devnet --dir ./devnet

Это полный узел: он проверяет каждый блок начиная с генезис-блока, обслуживает пиров и отвечает на RPC только для чтения на 127.0.0.1:9420. Чтобы подключиться к сети, которая действительно работает, см. Публичный testnet — и учтите, что для неё нужен бинарник -randomx.

Чего у узла нет#

В процессе узла никогда не бывает ключевого материала. Он не принимает ни семени, ни парольной фразы, ни файла ключа. Подписание происходит в zcd, а единственная точка записи у узла — /submit — не даёт никаких полномочий: поданный сертификат проверяется ровно так же, как пришедший от незнакомца.

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

Если вы ставите RPC за прокси, ограничивайте частоту на прокси

Ограничитель ключуется по транспортному пиру и никогда по X-Forwarded-For: ограничитель, доверяющий заголовку, который задаёт клиент, — это ограничитель, который клиент выключает. За прокси каждый запрос поэтому приходит с адресом прокси, так что лимит на клиента перестаёт быть лимитом на клиента и становится одной общей корзиной в 600 в минуту сразу на всех — и один вызывающий может опустошить её для всех. Это, пожалуй, хуже, чем отсутствие лимита вовсе, и ничто в ответе об этом не говорит: вызывающие получают обычный 429 независимо от того, они ли тому причиной или кто-то другой.

Поверхность только для чтения#

Всё, что смотрит на цепочку снаружи — обозреватель, монитор, индексатор — читает её здесь. Узел отдаёт байты и никогда — интерпретацию.

ТочкаОтвечает
/status, /headИдентичность вершины, высота, корень состояния
/block?height=N или ?id=0x…Один блок в JSON, с полями canonical, orphaned и confirmations
/block?…&format=sszТот же блок каноническими байтами SSZ, application/octet-stream
/paramsДействующий набор параметров и его корень консенсуса
/cell, /balanceСостояние вершины
/feesБазовые комиссии и действующие эластичные потолки
/mempoolСчётчики пула; ?limit=N добавляет до 1000 ожидающих идентификаторов, начиная с наименьшего
/network, /metricsФорма соединений и счётчики
/submitЕдинственная запись, и она ничего не даёт
Потолки не являются параметрами

§8.1 технического описания делает байтовый, газовый и сертификатный потолки блока функциями T, последовательной цели, а T — это консенсусное состояние, которым двигает контроллер эпох. Поэтому /params несёт только значения генезис-блока — block_byte_limit_genesis и подобные — которые задают, откуда T начинается и до какого пола может опуститься обратно, но никогда действующий предел. Эти числа остаются настоящими всегда, поэтому чтение одного из них как «предела размера блока» ошибочно молча и на величину вплоть до расстояния между значением генезиса в 2,5 МБ и потолком ёмкости в 8 МБ. /fees отдаёт живое T и четыре выведенных из него потолка, так что вывод можно проверить, а не принимать на веру.

Почему важны именно байты#

blake3("zcd/block/v1" ‖ header_bytes) — это идентификатор блока, поэтому наблюдатель, выводящий идентификаторы заново из того, что ему отдали, либо согласен с сетью, либо узнаёт об этом немедленно. Так же получаются и исходы по каждому сертификату: был ли сертификат применён, пропущен и оплачен или отброшен — вычисляется внутри свёртки и никогда не сохраняется, потому что одна строка на сертификат навсегда, в хранилище, живущем в памяти, — не то, что можно потребовать от узла нести. Наблюдатель, которому нужны исходы, сворачивает блок сам.

Чего здесь нет и не будет#

  • Никакого произвольного обхода состояния. Ни списка адресов, ни сканирования по префиксу, ни списка богатейших.
  • Никакого исторического состояния. /cell и /balance читают вершину; «баланс на высоте H» подразумевает удержание истории, которой узел не хранит.
  • Никаких административных вызовов, переиндексации или отладки, потому что привилегировать нечего.

Агрегация — дело того, кто наблюдает, и вычисляется один раз при приёме в его собственную базу данных.

Коды состояния#

Чтения отвечают на GET и HEAD и отвергают любой другой метод с 405. Корректный вопрос с отрицательным ответом — высота, до которой цепочка ещё не дошла, неизвестный идентификатор, тело блока, проигравшего реорганизацию — это 404; лишь некорректный запрос — это 400. Запись, которую узел записал и больше не может прочесть обратно, — это 500, а не 400: виноват диск узла, а сообщение вызывающему, что его запрос некорректен, побуждает его перестать повторять запрос, который всегда был корректным. Опрашивающему никогда не должно требоваться читать прозу, чтобы отличить отсутствие от ошибки или свою ошибку от узловой.

Реорганизации#

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

Достижимость и почему она важнее, чем кажется#

Zycord не занимается обходом NAT. Узел с --listen на достижимом порту — это место, откуда другие могут начать загрузку, а доля таких узлов — фальсифицируемое условие, на котором держится всё решение отказаться от обхода NAT.

# periphery: nothing to configure
zycordd --testnet --dir ./testnet

# core: bind a port, and make sure it is actually reachable
zycordd --testnet --dir ./testnet \
  --listen 0.0.0.0:9421 --advertise <public-address>:9421
Объявляйте адрес, который отвечает, либо не объявляйте никакого

--advertise откатывается к --listen, так что --listen 0.0.0.0:9421 без --advertise публикует 0.0.0.0:9421, куда никто не может дозвониться. Неверный адрес расходится через обмен пирами и стоит каждому узлу, который его пробует, одного дозвона. Если пробросить порт не получается, не указывайте --listen и будьте периферией.

Как проверить, что получилось#

curl -s localhost:9420/network
{"enabled":true,"peers":8,"listening":true,"inbound":5,"outbound":3,"reachable":true}

listening: true при inbound: 0 спустя несколько минут после запуска узла означает, что порт на деле недостижим: процесс привязан и ждёт, а ничего не приходит. Во всех прочих представлениях это выглядит здоровым — потому эта точка и существует.

Адреса начальной загрузки из файла#

zycordd --testnet --dir ./testnet --peers-file peers.txt

По одному адресу на строку; #-комментарии и пустые строки игнорируются, а список объединяется со встроенными seed-адресами сети. --peers принимает то же самое аргументом через запятую, а --no-seeds убирает встроенные, сохраняя оба источника.

Интерфейс кошелька через ssh-туннель#

Узел не держит ключа и никогда не будет. Кошелёк — отдельный процесс, а на сервере это zcd ui:

zcd ui --key wallet.json --no-open

Это печатает URL и отдаёт его на 127.0.0.1:9430. Он привязывается к петлевому интерфейсу и отвергает любой другой, и флага для обхода нет. Процесс за этим слушателем держит разблокированный закрытый ключ и аутентифицируется токеном-предъявителем в URL, чего достаточно для сокета, который может открыть только локальная машина, и недостаточно для чего-либо ещё. Доступ к нему из других мест — задача ssh, и ssh справляется с ней лучше, чем справилось бы это:

# on your own machine
ssh -L 9430:127.0.0.1:9430 <host>

Затем откройте URL, напечатанный сервером. Токен едет во фрагменте URL, поэтому никогда не доходит до сервера, журнала или заголовка Referer — относитесь к URL как к секрету, которым он и является. Он новый при каждом запуске, а Ctrl-C стирает ключ и прекращает обслуживание.

Локальный конец проброса не обязан быть портом 9430: интерфейс проверяет имя хоста в заголовке Host и намеренно не проверяет порт. Значение имеет имя хоста — именно оно блокирует перепривязку DNS, а это настоящая атака на сервер на петлевом интерфейсе, поскольку любая страница в вашем браузере может слать запросы на 127.0.0.1, и единственное, чего она подделать не может, — это имя, по которому на неё перешли.

Не ставьте обратный прокси перед zcd ui

Совет выше про RPC узла касается поверхности, которая не держит ключа и не даёт никаких полномочий. Эта — держит ключ. Не существует варианта её открытия, который был бы хорошей идеей, а туннель стоит одного флага.

Ещё две формы, о которых стоит знать:

  • zcd ui --locked запускается, не спрашивая парольную фразу в терминале; вместо этого спрашивает браузер. Полезно, когда терминал общий или журналируется.
  • zcd ui --lock-after 5m сокращает блокировку по простою, которая стирает ключ на месте — семя перезаписывается, а не просто теряет ссылку.

Идентичность пира и анонимность#

Узел генерирует новый ключ пира Ed25519 при каждом запуске и никогда не записывает его на диск. Он ни из чего не выводится — ни из вашего кошелька, ни из семени, ни из чего-либо ещё на машине. Перезапуск его меняет.

Если вы запускаете узел так, что это для вас важно, деанонимизирует вас не ключ. Деанонимизирует адрес. Узел начальной загрузки, запущенный вами на инфраструктуре, которую можно связать с вами, — это вектор деанонимизации, который никакая гигиена ключей не исправит.

Каталог данных#

data/
  chain/       blocks, state, and the write-ahead log
  peers.json   the persisted peer store

Хранилище пиров сохраняется на диск намеренно: узел, начинающий с чистого листа после каждого перезапуска, даёт атакующему свежий шанс его заполнить. Удалить его безопасно, но это выбрасывает такую защиту.

Узел переживает убийство в любой момент — для этого и нужен журнал упреждающей записи, и это проверяется убийством узлов случайным образом при сетевом хаосе. Он не переживает правку своего каталога данных у себя под ногами: при запуске он пересчитывает корень состояния и отказывается работать, если сохранённый расходится.

Выбор сети#

ФлагСетьИдентификатор цепочкиДвижок
(нет)mainnet1randomx-v1
--testnetпубличный testnet2randomx-v1
--devnetлокальный devnet1337отладочный движок

Параметры встроены в бинарник, а не читаются по пути. Каждый участник публичной сети обязан нести те же байты, иначе они не в одной сети, а файл, переданный через --params, — это файл, который расходится. Другая сеть — это другая сеть: две такие разъединяются на рукопожатии, а каталог данных с чужой цепочкой отвергается при запуске, а не смешивается молча.

Восстановление повреждённого каталога данных#

zycordd несёт в себе путь починки для каталога данных, против которого узел отказывается стартовать. Сначала остановите узел — zycordd repair --dir ./data берёт блокировку каталога и отказывается работать, пока её держит узел, — а затем, прежде чем что-либо менять, спросите --dry-run, что произошло. На месте чинится только один класс повреждений; для остальных лекарство — пересинхронизация, и именно сухой прогон говорит, какой класс у вас.