ZYCORD docs
Русский
ZycordДокументацияАрхитектура

Архитектура

Инженерный спутник технического описания — как эталонный узел воплощает замысел и какие решения за этим стоят. Пояснительно, не нормативно.

Этот документ не является нормативным

Протокол — это файлы параметров и эталонные векторы, плюс именованные правила везде, где они определены; спецификация сетевого протокола несёт требования слоя пиров. Эта страница объясняет ту поверхность и фиксирует решения, стоящие за ней. Там, где она расходится с нормативной поверхностью, побеждает нормативная поверхность, а этот текст исправляется. Полный инженерный спутник — docs/ARCHITECTURE.md.

Два предиката, два движка#

Жизнь сертификата от начала до конца:

 wallet                      network                        every full node
+------------+   gossip   +--------------+               +---------------------+
| build cert | ---------> | cert topic   | ------------> | STATELESS PIPELINE  |
| (reads,    |            | (TLS gossip) |               | V1..V8, batch sigs, |
| writes,    |            +--------------+               | native re-exec      |
| sigs, seq, |                                           |  -> mark VALID      |
| deposit)   |                                           +----------+----------+
+------------+                                                      | mempool
                                                                    v
                            miner (any node)              +---------------------+
                          +-------------------+  block    | FOLD (sequential)   |
                          | assemble ordered  | --------> | F-rules per cert:   |
                          | hash list + bodies|  gossip   | APPLY / SKIP / DROP |
                          | + RandomX solve   |           |  -> new state       |
                          +-------------------+           +---------------------+

Действительность — без состояния и параллельна: она вычисляется один раз на сертификат на узел, до блоков и независимо от них. Применимость — с состоянием и последовательна: она вычисляется внутри свёртки на зафиксированной позиции сертификата. Майнер не выполняет исполнения; он упорядочивает хеши, действительность которых он уже видел проверенной, и решает доказательство работы. Свёртка — это плотный цикл по рабочему множеству в памяти — сравнить, прибавить, записать.

Инженерные принципы#

Принцип
P1Свёртка священна. Функция перехода состояния живёт в одном чистом пакете без ввода-вывода, без часов, без горутин, без обхода отображений и без плавающей точки. Это единственный код, ошибки которого нельзя исправить задним числом. Всё остальное в узле — заменяемая обвязка.
P2Детерминизм важнее производительности. Любая оптимизация, рискующая недетерминизмом, в консенсусном коде отвергается. Производительности место в конвейере без состояния, где она безопасна.
P3Никаких административных ключей, никакого привилегированного RPC. Не существует пути в коде, которым какой-либо ключ мог бы приостановить, обновить, выпустить или реорганизовать. Чего нет в правилах свёртки, того не существует.
P4Малая поверхность консенсуса. core/ не импортирует ничего вне стандартной библиотеки. Остальной узел может пользоваться библиотеками экосистемы; ядро — нет.
P5Сначала спецификация. Эталонные векторы и есть протокол. Код на Go — это одна из эталонных реализаций; независимая реализация, проходящая векторы, — равноправный участник, а не форк. Именно это позволяет сопровождающему в итоге быть никем в частности.
P6Воспроизводимость начиная с v0.1. Закреплённый набор инструментов Go, -trimpath, закреплённые зависимости. Доверие переносится с бинарника на код, а это единственное доверие, которое может предложить анонимный автор.
P7Один бинарник, открываемый высотой. Машина, залоговые операции и регистрация секвенсоров компилируются в каждый выпуск и отклоняются ниже своей высоты активации — Validate требует h1_vm ≥ h1_bond и требует, чтобы h1_vm приходилась на границу эпохи. Эра наступает потому, что цепочка дошла до некоторого числа, и никогда потому, что операторов попросили обновиться.

P3 — тот принцип, на который скептический читатель должен давить сильнее всего, а казна — то место, где давить: генезис-блок не содержит ни ключа, ни какого-либо пути расходования вовсе, так что нет и привилегии, которую можно было бы держать, делегировать или украсть. Кворум 3 из 5, который вводит эра Era 2, закрепляется будущим хардфорком — тем же социальным механизмом, что и любое другое изменение консенсуса, подлежащим тому же отказу и способным сдвинуть ровно одну ячейку даже тогда. Кворум, который появляется, только если сеть согласится его вписать, и который затем держит одну ячейку, — это правило расходования. Административный ключ — это ключ, существующий до чьего-либо согласия и дотягивающийся до всего.

Криптографические примитивы#

РольВыборПочему
ПодписиEd25519Пакетная проверка (пандус для GPU), отсутствие ковкости, крошечные ключи. Строгие правила, зафиксированные в генезис-блоке: канонические кодировки обязательны для открытого ключа и R, открытый ключ свободен от кручения и не малого порядка, а проверка ведётся без кофактора.
ХешированиеBLAKE3Идентификаторы сертификатов и блоков, адреса, корень состояния. Достаточно быстр, чтобы хешировать на скорости линии ретрансляции; дружелюбен к параллелизму для корня состояния эпохи.
Доказательство работыRandomXОптимизирован под CPU. Единственный cgo в дереве, за тегом сборки, и отсутствующий в сборке без него. pow_engine входит в корень консенсуса, поэтому бинарник с неверным движком отказывается запускаться, а не принимает неверное доказательство.
Разделение доменовобязательноКаждый хеш — это blake3(tag ‖ payload). Подписи подписывают идентификатор цепочки и корень консенсуса, что убивает повтор и между сетями, и между двумя воплощениями одной сети.

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

Каноническая кодировка и идентификаторы#

Все консенсусные объекты — это контейнеры SSZ: единая каноническая байтовая кодировка, никакого порядка отображений, никакой неоднозначности необязательных полей, фиксированные смещения для дешёвого частичного разбора и нативная мерклизация.

cert_id       = blake3("zcd/certid/v1" || ssz(certificate with an empty signature list))
cert_exemplar = blake3("zcd/cert/v1"   || ssz(certificate))
block_id      = blake3("zcd/block/v1"  || ssz(header))

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

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

Ставка комиссии находится внутри идентификатора, и это денежное решение

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

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

Модель ячейки#

Ячейка — это значение в слоте. Значения ячеек — беззнаковые 256-битные целые, хранимые с обратным порядком байтов в 32 байтах. Отсутствующие ячейки читаются как ноль — ноль — это отсутствие, и это требование консенсуса, а не удобство реализации: оно делает корень состояния функцией состояния, а не истории, его породившей.

Отдельно протокол ведёт реестр израсходованных адресов: постоянное консенсусное множество одноразовых адресов, чьи полномочия на подпись сожжены.

Addr = version || blake3("zcd/addr/v1" || version || payload)[:31]
ВерсияВидРазрешение на списание
0x01одноразовый пользовательскийПодпись владельца; любой списывающий сертификат обязан также нести явный MARK_SPENT. После его применения любое чтение и любая запись под этим адресом навсегда завершаются отказом.
0x02постоянный пользовательскийПодпись владельца; многоразовый навсегда. Никогда не может попасть в реестр израсходованных.
0x03активУправляется неизменяемыми ячейками полномочий актива.
0x00протокольныйТолько для свёртки: маяк эпохи, ячейки базовой комиссии, кольцо созревания coinbase, ячейка казны.
0x04зарезервировано — ячейка со скрытым значением (эра Era S)Недостижимо, пока идёт эра Era 0.

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

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

Действительность без состояния и закон тарификации#

V-правила выполняются для каждого сертификата, параллельно, до приёма в мемпул и во время проверки блока, и не требуют состояния вовсе: каноническая форма и идентификатор цепочки; проверка каждой подписи над корнем подписания; выводимость разрешения из одного лишь сертификата; равенство заявленных чтений тому, что выводит программа; и сверка адресата возврата с тем, что сжигает сам сертификат.

Закон тарификации в системе умещается в одно предложение, и держаться стоит именно за него:

Одна подпись, не более одного счёта, и никогда на позиции, которой её подписант не мог избежать.

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

Структура репозитория#

zycord/
  spec/       parameter sets, golden vectors, library images   <- THE PROTOCOL
  core/       consensus-critical; standard library only (P4)
    types/  crypto/  ssz/  u256/  state/  validity/  fold/  params/  genesis/
    cevm/          the certificate-adapted EVM; vendored interpreter, pure Go
    stdlib/        the pre-deployed library, its addresses and code hashes
    pow/randomx/   the mainnet engine: vendored C++, cgo, behind a build tag
  node/       storage/  chain/  verify/  mempool/  miner/  p2p/  sync/  rpc/  stratum/
  wallet/     key management, certificate builders (reference; not consensus)
  contracts/  the reference contracts, in Solidity
  sim/        simulator, fuzz harnesses, differential refold, chaos soak
  cmd/        zycordd, zcd
  desktop/    the wallet in a native window — a separate Go module
  docs/       architecture, protocol, operating guide, whitepaper

Стрелки зависимостей направлены только внутрь — node → core, wallet → core, и никогда наоборот — и ничто внутри core/ не дотягивается за пределы core/ и стандартной библиотеки. Одно исключение, названное и обеспеченное: core/pow/randomx — единственный cgo в дереве, и он компилируется только под тегом сборки, поэтому любая сборка без тега по-прежнему обходится одной стандартной библиотекой и не требует набора инструментов C. CI выполняет проверку, которая это обеспечивает, потому что проверка сторонних зависимостей ищет пути модулей, а у cgo их нет — из-за чего правило не обеспечивалось никем, пока эту проверку не добавили.

Как это тестируется#

  • Эталонные векторы. У каждого правила свёртки, блока и действительности есть положительные и отрицательные случаи в виде (pre-state, block) → (post-state | invalid, outcomes, fees). Этот набор — контракт совместимости для независимых реализаций.
  • Набор на злоупотребления. Повторное включение применённых и пропущенных сертификатов, включение истёкших, включение с заниженной ставкой, зависимые цепочки при перетасовке предлагающим, циклы сжигания и возврата, штормы сторонних зачислений, гонки на границе лимита эмиссии.
  • Тесты на свойствах. Детерминизм свёртки при перестановке порядка предлагающим, коммутативность дельт, устойчивость к ABA и сохранение — включая депозиты в пути, кольцо созревания и ячейку казны, поскольку проверка сохранения, пропускающая казну, сообщает о создании стоимости в каждом блоке.
  • Дифференциальные. Намеренно наивная вторая реализация свёртки, написанная ради очевидности, а не скорости, фаззится против настоящей. Расхождение блокирует релиз.
  • Состязательное моделирование. Штормы пропусков, гонки на опустошение депозита, майнеры, набивающие блок под отбрасывание, пытка реорганизациями, манипуляции метками времени, облегчённые сценарии затмения ретрансляции. Конфигурации сценариев зафиксированы в репозитории; запуски воспроизводимы по семени.
  • Параллелизм, намеренно. У каждого компонента, которого касается более одной горутины, есть тест, который сам параллелен, и в той форме, в какой это делает настоящий процесс. -race на однопоточном наборе ничего не измеряет и сообщает об успехе.
  • Хаос-прогон. Настоящие процессы узлов на настоящих сокетах за прокси, вносящим задержку, джиттер, потери и разделения, с узлами, убиваемыми SIGKILL случайным образом. Именно эта поверхность нашла гонку данных, которую весь набор с -race упустил.

Генезис-блок — это артефакт, а не церемония#

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