Gonka GitHub Discussions · Discussion #1340

`devshard` Height-sync protocol

Отдельная страница дискуссии с русским переводом и параллельным режимом RU / Original с точным сопоставлением предложений.

Как пользоваться: включите RU / Original и кликните по предложению — соответствующее предложение в другой колонке доскроллится и подсветится.
Discussion #1340

`devshard` Height-sync protocol

Оригинал: `devshard` Height-sync protocol

alexanderkuprin avatar
alexanderkuprinАвтор

Протокол синхронизации по высоте

Конверты пользователя ↔ хоста содержат дополнительный HeightSyncSection, который подтверждает тройку основной сети (height,block_hash,block_timestamp). Этот раздел является единственным вводом для межузлового выравнивания времени, принятия решений о тайм-ауте и предиката IsStrictlyConfirmed(h), который выносит вердикт нисходящим протоколам (cPoC, финализация). Block_hash может быть источником детерменистической случайности, которая заранее неизвестна, может использоваться VALIDATION_PROTOCOL_PROPOSAL.md (https://github.com/a-kuprin/gonka/blob/devshard-testenv/devshard/docs/proposals/VALIDATION_PROTOCOL_PROPOSAL.md). Существуют и другие варианты источника случайности, которые необходимо обсудить.

block_timestamp может быть детерминированным временем, которое будет использоваться в devshard для таймаутов.

Этот документ является канонической спецификацией одной версии. Реализация в дереве соответствует этому документу; в каталоге тестов (height-sync-tests.md (https://github.com/a-kuprin/gonka/blob/devshard-testenv/devshard/docs/height-sync-tests.md)) указано, что уже доказано и что планируется.

Связанные документы: height-sync-tests.md (https://github.com/a-kuprin/gonka/blob/devshard-testenv/devshard/docs/height-sync-tests.md) (каталог тестов — реализован и запланирован), протокол CPOC для devshard (https://github.com/gonka-ai/gonka/discussions/1384), протокол финализации (https://github.com/gonka-ai/gonka/discussions/1369), VALIDATION_PROTOCOL_PROPOSAL.md (https://github.com/a-kuprin/gonka/blob/devshard-testenv/devshard/docs/proposals/VALIDATION_PROTOCOL_PROPOSAL.md).

Оглавление

Резюме (#1-резюме)

Проблема (№2-проблема)

Общий обзор протокола (#3-высокоуровневый обзор протокола)

Голы (#4-голы)

Глоссарий (#5-глоссарий)

Обзор архитектуры (#6-обзор архитектуры)

Формат провода (#7-формат провода)

Режимы синхронизации (Omit / Anchor / Strong) (#8-sync-modes-omit--anchor--strong)

Каденция (синхронизация поворотов, K, slots_num, принудительные повороты) (#9-каденция)

Правила производителя (#10-producer-rules)

Приемный конвейер (№ 11-приемник-конвейер)

Модель доверия и подписи (#12-trust-model-and-signatures)

Перенос, происхождение, атрибуция (# 13-перенос-происхождение-атрибуция)

API подтверждения ( IsStrictlyConfirmed ) (#14-confirmation-api)

Интеграция cPoC — полный API (#15-cpoc-integration-api)

Модель атаки и меры по ее устранению (#16-модель атаки)

Значения по умолчанию и конфигурация (#17-defaults-and-configuration)

Статус и вехи (#18-статус-и-вехи)

1. Резюме

Трафик вывода пользователь-хост содержит тело HTTP, состоящее из двух разделов:

  • HeightSyncSection — дополнительная аттестация основной сети: подписанная пара (mainnet_height, mainnet_block_hash), а также метаданные кадрирования и происхождения.
  • message_body — полезная нагрузка приложения (непрозрачна для синхронизации по высоте).

Раздел 1 выдается только при необходимости:

  • Синхронизация хода — стандартная каденция: каждые K одноразовых номеров, окно последовательных одноразовых номеров slots_num несет в себе Anchor; между ними, Омит.
  • Принудительный поворот синхронизации — MsgForceHeightSyncTurn в любой момент открывает диапазон привязки slot_num (открывается спор cPoC, принудительное вмешательство оператора).
  • |Δ| > D — когда заявленная высота отправителя отличается от выровненной высоты получателя более чем на D, отправитель ДОЛЖЕН использовать Strong ( LightBlock + VerifyCommit ); в противном случае получатель отклоняет.

Хосты подписывают якоря ответной ветви своим ключом подписывающего лица secp256k1; пользователи-курьеры переносят эти подписанные BLOB-объекты вперед, проверяя при приеме и используя их в качестве доказательства невиновности по требованию. Якорям ветки запроса доверяют хосты (без встроенной подписи) — в случае спора пользователь доказывает происхождение позже.

Одиночный предикат IsStrictlyConfirmed(h) предоставляет нижестоящим потребителям дискретный ответ {подтвержденный, ожидающий, устаревший}.

2. Проблема

В devshard нам нужен источник высоты и случайности основной сети, который заранее неизвестен, но является детерминированным, чтобы заставить все хосты и пользователя согласиться.

Каждый хост имеет свой собственный последний (высота, block_hash) оракул (цепочка вывода grpc) и подписи валидаторов цепочки вывода. Но хосты должны согласиться с тем, что любая высота, указанная в протоколе, действительно является последней. Таким образом, в этом документе должен быть указан протокол синхронизации по высоте.

3. Общий обзор протокола

Поскольку devshard рассчитан на высокую пропускную способность, мы стремимся свести к минимуму дополнительные данные, передаваемые в сообщениях, и свести к минимуму проверки подписей Minnet. Также мы минимизируем сплетни, которые должны возникать только в случае споров и урегулирования, чтобы минимизировать трафик (поскольку один хост может находиться во многих devshard'ах).

Итак, основные дизайнерские решения приняты:

  • Синхронизация высоты происходит не при каждом nonce, а только в специализированных окнах, где мы добавляем только к запросу/ответу (height,block_hash) и подпись отправителя (хоста, отвечающего на запрос), чтобы доказать происхождение этих данных для возможных споров. Пользователь переносит (высоту, block_hash) при следующих запросах для распространения этих данных на другие хосты.
  • Мы доверяем высотам в будущем без дополнительных доказательств в сети, если высота близка к той, которая, как мы знаем, является текущей. Если между хостами существуют большие разногласия по высоте ( height_in_the_future -known_height = |Δ| > D ), мы используем полные данные из основной сети (хэш блока и подписи валидаторов) для проверки высоты.
  • Если мы обнаружим, что ранее предоставленная каким-либо хостом высота не соответствует оракулу block_hash, мы начинаем спор.

В результате мы предоставляем API для devshardd и devshardctl, который выдает последнюю высоту, хэш блока и информацию, если с этим согласны большинство участников сети devshard.

4. Цели

  • Дешевое периодическое выравнивание — Anchor (без LightBlock ) по расписанию синхронизации позволяет каждому хосту видеть время основной сети в ограниченном окне без накладных расходов на проверку каждого сообщения.
  • Сильная эскалация разногласий — однажды |Δ| > D или финализация требует этого, доказательство с привязкой к кворуму валидатора ( LightBlock ) является обязательным.
  • Происхождение и атрибуция — каждый кэшированный (H, хеш) можно проследить до подписи исходного хоста; операторы связи не могут быть обвинены в пересылке подписанного заявления злонамеренного хоста, а операторы связи, которые раскрывают происхождение, становятся источником шифрования.
  • Устойчивость к повторному воспроизведению — бюджет актуальности F по временной метке отправителя + учет последней распространенной информации для каждого получателя не позволяет оператору повторно использовать устаревшие или уже доставленные чаевые.
  • Контракт подтверждения для последующих потребителей — дискретный предикат IsStrictlyConfirmed(h) ∈ {confirmed, pending, stale}, поэтому cPoC/финализация не изобретают собственную логику кворума.
  • Развертывание только через курьера — пользователи, у которых нет собственных последователей в основной сети, по-прежнему могут передавать подписанные подсказки хоста между хостами в циклическом переборе и подтверждении охвата (C-quorum).

5. Глоссарий

6. Обзор архитектуры

блок-схема LR основная сеть подграфа [основная сеть Gonka] BC[Консенсус CometBFT] end BC -- "заголовки блоков + фиксации" --> HSD[heightsyncd /blockoracle] хост подграфа [время выполнения хоста] HSD --> SCH_H[AnchorScheduler<br/>локальный источник Oracle] SCH_H --> TX_H[transport.Server<br/>подписывает ответную часть] TX_H -- «HeightSyncSection<br/>входящий запрос» --> RX_H[Конвейер получателя<br/>D-диапазон, актуальность, классификация] RX_H --> AUD_H[AuditRing + ConfirmationIndex] AUD_H -- «IsStrictlyConfirmed» --> CPOC_H[потребитель cPoC] конечный пользователь подграфа [Courier user / devshardctl] TIPS[HeightSyncPeerTips<br/>дословно подписанные BLOB-объекты] --> SCH_U[AnchorScheduler<br/>источник одноранговых подсказок] SCH_U --> TX_U[transport.HTTPClient<br/>Перенести, снять подпись по запросу] TX_U -- "входящий ответ<br/>проверить + кэш" --> RX_U[Проверить ответ Anchor<br/>RecordOriginWithBlob] RX_U --> TIPS TIPS -- "IsStrictlyConfirmed" --> CPOC_U[потребитель cPoC] end TX_U -- "ветвь запроса<br/>HeightSyncSection" --> RX_H TX_H -- "ветвь ответа<br/>HeightSyncSection (подписанная)" --> RX_U

Ключевые инварианты:

  • У каждого хоста есть собственный подписчик в основной сети (heightsyncd /blockoracle); это канонический источник local_aligned.
  • У пользователя нет подписчиков (режим курьера); он извлекает local_aligned из проверенного однорангового кэша, заполненного подписанными ответами хоста.
  • HeightSyncSection — единственная поверхность проводов, связанная с основной сетью, на конвертах вывода; приемный трубопровод – однопоточный.

7. Формат провода

HeightSyncSection передается как поле protobuf в конверте вывода и зеркально отображается в формате JSON для инструментов. Номера полей стабильны.

Примечания:

  • Деградированный якорь (тихая подача). Если локальный оракул не получил новый блок в StaleAfter, но Latest() по-прежнему возвращает кэшированный заголовок, хосты выдают обычный Anchor (поля 1–8) плюс поле 10. Это позволяет избежать реакции синхронизации-поворота. Пропустить во время длинных промежутков между блоками; консенсус среди хостов по-прежнему исправляет меньшинство устаревшим советом. Пропустить остается обязательным, если нет кэшированной подсказки (канал никогда не запускался), происходит сбой Latest() (канал недоступен) или кеш одноранговых подсказок курьера пуст.
  • Подписи, привязанные к направлению. Поле 8 устанавливается хостами только в ответах. Carry() очищает поле 8 перед отправкой части запроса; Проверка входящего запроса не требует встроенной подписи.
  • Канонический ввод подписи. CanonicalOriginBytes(sec) = "heightsync.origin.v1" || прото.Маршал(поля 1..7) . Поле 8 не является частью входных данных для подписи.
  • Резервирование на уровне проводов. origin_attestation (встроенный исходный объект) зарезервирован для будущих встроенных развертываний; текущий протокол использует асимметричную модель (подписанный ответ, доверенный запрос, оправдание по требованию).

Зеркало JSON:

{ "height_sync": { "proof_type": "height-anchor-v1", "mainnet_height": 42, "mainnet_block_hash_hex": "abc... ", "timestamp_unix_ms": 1700000000000, "направление": "ответ", "originator_sender_id": " gonka1host... ", "originator_timestamp_unix_ms": 1700000000000, "sender_signature": "base64... " , "light_block" : " base64... ", "tip_stale_after_ms": 12000 } }

( Tip_stale_after_ms опускается, если кэшированная подсказка свежая.)

8. Режимы синхронизации (пропустить/привязать/сильный)

stateDiagram-v2 направление LR [*] --> Пропустить Пропустить --> Привязка: одноразовый номер при синхронном повороте/принудительном повороте/ленивом переносе Привязка --> Пропустить: следующий одноразовый номер за пределами окна Привязка --> Строгая: \|H − local_aligned\| > D ИЛИ принудительное (StrongRequired) Сильное --> Привязка: партнеры перестроены, снова внутри D Привязка --> Привязка: частота шагов на следующем ходу Сильная --> Сильная: неподвижно > D

Периодическое выравнивание использует только Anchor. Сильный – это не шаг по умолчанию, а путь разногласий/споров.

Тихий канал против мертвого канала (хосты):

9. Каденс

Синхронно-поворотные окна

Для направления сеанса для исходящего nonce n:

  • Начальный ход синхронизации: 1 ≤ n ≤ slots_num → Anchor (или Strong, см. §10).
  • Периодические циклы синхронизации: для каждого i ≥ 1 i·K ≤ n ≤ i·K + slot_num − 1 → Anchor.
  • Все остальные одноразовые номера → Опустить, если не применяется принудительная директива или отложенный перенос.

Ограничение: K ≥ slot_num, чтобы окна никогда не перекрывались.

Gantt title Частота синхро-поворотов (K=8, slot_num=4) dateFormat X axisFormat %s раздел Каденция Начальный синхро-поворот (Привязка) :a1, 1, 4 Пропустить :a2, 5, 7 Периодический синхро-поворот 1 :a3, 8, 11 Пропустить :a4, 12, 15 Периодический синхро-поворот 2 :a5, 16, 19

Принудительный поворот синхронизации

MsgForceHeightSyncTurn(trigger_nonce, slots_num, Reason,strong_required?) открывает диапазон ActiveForcedTurn{start, end}:

  • Оба направления ДОЛЖНЫ излучать Anchor для каждого конверта в [start, end] . Пропустить внутри принудительного поворота НЕДЕЙСТВИТЕЛЬНО.
  • Strong_required = true обновляет окно до Strong.
  • Вторая директива, пока ход активен, молча игнорируется.
  • Принудительное окно, которое перекрывает следующее окно каденции, поглощает его (нет двойной привязки на границе).
  • После n > end каденция возобновляется по стандартному правилу.

Отложенный перенос (курьерские развертывания)

За пределами любого окна синхронизации-поворота пользователь-курьер МОЖЕТ выдать Anchor на этапе запроса iff:

  • Одноранговый кеш содержит новый раздел отправителя ( MaxFresh(now, F) возвращает ненулевое значение).
  • кэшированная_максимальная_высота > Last_propagated[получатель] .

Получатель классифицирует это как VALID_LAZY_ANCHOR (тег аудита lazy ); он не открывает обязательство синхронизации-поворота.

10. Правила продюсера

Хосты (есть собственный оракул)

  • При каждом исходящем ответе: обратитесь к местному оракулу; если применяется синхронный или принудительный поворот, выдайте Anchor с OriginatorSenderID = host_address , OriginatorTimestampMs = now и подпишите раздел (поле 8). Если оракул молчит (нет нового блока внутри StaleAfter), но существует кэшированный совет, все равно выдайте этот Anchor и установите для Tip_stale_after_ms возраст последнего принятого блока (поле 10 устанавливается после подписания полей ввода 1–7). Пропускайте только в том случае, если нет полезной кэшированной подсказки или происходит сбой Latest().
  • Если установлен Force.StrongRequired ИЛИ значение Peer_aligned_height получателя отличается от локального наконечника на > D: создайте Strong, присоединив кэшированный LightBlock для H (поле 9).
  • По входящим запросам: ничего не подписывайте; классифицировать через приемный конвейер (§11).

Пользователь-курьер (нет собственного оракула)

  • Поддерживайте HeightSyncPeerTips с ключом OriginatorSenderID .
  • Проверка ответов хоста при приеме ( VerifyOrigin ); в случае неудачи отбросьте подсказку и увеличьте origin_sig_invalid_total.
  • По исходящим запросам: обратитесь к планировщику; ленивый перенос только тогда, когда в кеше есть подсказка, еще не переданная получателю. Перед отправкой очистите поле 8 (sender_signature).
  • Производитель никогда не устанавливает OriginatorSenderID = user_address; это поле отражает хост, подписавший кэшированный большой двоичный объект.

11. Ресиверный конвейер

блок-схема TD A[конверт прибывает] --> B{HeightSyncSection<br/>присутствует?} B -- нет --> O{nonce в синхронном повороте /<br/>активный принудительный поворот?} O -- да --> O1[INVALID<br/>sync_turn_anchor_missing] O -- нет --> O2[VALID_OMIT] B -- да --> C{Привязка или сильная?} C -- Привязка --> D {"|H − local_aligned| > D?"} D -- да --> D1[НЕВЕРНО<br/>strong_required] D -- нет --> E{перенести<br/>набор исходного источника?} E -- да --> F{оригинатор в F?} F -- нет --> F1[НЕВЕРНО<br/>stale_required] F -- да --> G[классифицировать каденцию / ленивый<br/>по nonce vs синхронизировать поворот] E -- нет --> G C -- Strong --> H[StrongVerifier.VerifyLightBlock] H -- ok --> I[VALID_STRONG] H -- неудачно --> H1[INVALID<br/>strong_proof_invalid] G --> J{блок H локальный И<br/>хеш совпадает?} J -- да/совпадение --> K[VALID_ANCHOR или<br/>VALID_LAZY_ANCHOR] J -- нет/local-missing --> L[поставить в очередь отложенную проверку] J -- локальное И несоответствие --> M{присутствует отправитель?} M -- да --> M1[DISPUTE_ORIGINATOR] M -- нет --> M2[DISPUTE_CARRIER]

Нормативные шаги для конверта без пропуска:

  • Парсинг + кадрирование (прото/JSON).
  • Сначала проверьте вынужденный поворот. Если ActiveForcedTurn[start..end] установлен и start ≤ nonce ≤ end, конверт ДОЛЖЕН быть Anchor (или Strong, если StrongRequired). Пропустить ⇒ НЕДЕЙСТВИТЕЛЬНО.
  • Группа Д. Еслиproof_type == "height-anchor-v1" и |H − local_aligned| > D: НЕВЕРНО (strong_required).
  • Сильный путь. Еслиproof_type == "cometbft-light-block-v1": запустите StrongVerifier.VerifyLightBlock (идентификатор цепочки, заголовок против утверждений, validators_hash, необязательный шаг 3b с привязкой к эпохе, BlockID, фиксация > 2/3); неудача ⇒ НЕДЕЙСТВИТЕЛЬНО (strong_proof_invalid).
  • Наличие оригинатора и свежесть. Если OriginatorSenderID != "" : Если now_ms − OriginatorTimestampMs > F ⇒ НЕДЕЙСТВИТЕЛЬНО (stale_origin); доверие аудита = TrustDisputeCarrier . Остальное продолжайте.
  • Если now_ms – OriginatorTimestampMs > F ⇒ НЕДЕЙСТВИТЕЛЬНО (stale_origin); доверие аудита = TrustDisputeCarrier .
  • Остальное продолжайте.
  • Каденция/ленивая классификация. Внутренняя синхронизация-поворот (каденция/начальная/принудительная): VALID_ANCHOR (каденция тега). Внешняя синхронизация-поворот + присутствует отправитель (курьер): VALID_LAZY_ANCHOR (тег lazy ). Внешняя синхронизация + отправитель отсутствует + Привязка: самоаттестация устаревшего хоста; VALID_ANCHOR .
  • Внутренняя синхронизация-поворот (каденция/начальная/принудительная): VALID_ANCHOR (каденция тега).
  • Внешняя синхронизация-поворот + присутствует отправитель (курьер): VALID_LAZY_ANCHOR (тег lazy ).
  • Внешняя синхронизация + отправитель отсутствует + Привязка: самоаттестация устаревшего хоста; VALID_ANCHOR .
  • Согласование местного оракула. Если блок H локальный и хэш совпадает → подтверждается немедленно; фид ConfirmationIndex. Если H еще не является локальным → поставить в очередь отложенную проверку по (оригинатор, H, хеш); не увеличивайте height_seen_max. Если H является локальным и хеш-код отличается → DISPUTE_ORIGINATOR (присутствуют метаданные отправителя) или DISPUTE_CARRIER (отсутствует отправитель или подпись не удалась); сохранить оскорбительный подписанный BLOB-объект.
  • Если блок H локальный и хэш совпадает → подтверждается немедленно; фид ConfirmationIndex.
  • Если H еще не является локальным → поставить в очередь отложенную проверку по (оригинатор, H, хеш); не увеличивайте height_seen_max.
  • Если H является локальным и хеш-код отличается → DISPUTE_ORIGINATOR (присутствуют метаданные отправителя) или DISPUTE_CARRIER (отсутствует отправитель или подпись не удалась); сохранить оскорбительный подписанный BLOB-объект.
  • Аудит + метрики. Добавьте AnchorAttestation (с Tag , Trust , OriginatorSenderID , OriginSignedBlobAvailable ) к одноранговому кольцу; выдавать счетчики.
  • Обработать message_body, если оно не INVALID.

Классы результатов

12. Модель доверия и подписи

Протокол асимметричен: ответы подписываются, запросы доверяются, оправдание происходит по требованию.

Участник SequenceDiagram U как пользователь (курьер) Участник H как хост A Участник H2 как хост B U->>H: запрос (участок запроса, без подписи) H->>U: ответ Привязка [подписанная хостом A] Примечание над U: VerifyOrigin OK<br/>RecordOriginWithBlob(host_A, H, blob, sig) U->>H2: привязка запроса (перенос вперед, без встроенной подписи) Примечание через H2: доверяет несущему<br/>применяется шлюз свежести F. Примечание над H2: позже ведомый продвигается<br/>сравнивает хэш с каноническим H2 -->>U: DEFERRED_FAIL? открытый спор против пользователя U->>U: HeightSyncEvidenceFor(host_A, H) → blob + sig U-->>H2: Signed_blob доказывает отправителя = хост A<br/>⇒ DISPUTE_ORIGINATOR против хоста A

Этап ответа (хост → пользователь)

  • Хост заполняет OriginatorSenderID, OriginatorTimestampMs, создает CanonicalOriginBytes (поля 1–7 + домен heightsync.origin.v1).
  • Подписывается ключом secp256k1 хоста, устанавливает поле 8.
  • Пользователь проверяет поле 8 при приеме. Ошибка ⇒ удаление, нет кэша, нет распространения; origin_sig_invalid_total увеличивается.
  • В случае успеха: RecordOriginWithBlob(originator, sec, blob, sig) .

Ветка запроса (пользователь → хост)

  • Перенос копирует поля отправителя (6, 7) из кэшированного большого двоичного объекта.
  • Carry() удаляет поле 8 перед отправкой.
  • Хост принимает раздел, соответствующий конвейеру-получателю (§11). Никакая встроенная подпись не требуется и не проверяется.

Оправдание

Если позже хост открывает спор против пользователя-перевозчика, пользователь вызывает HTTPClient.HeightSyncEvidenceFor(originator, H) для создания кэшированного (blob, sig) . Средство проверки спора повторно запускает VerifyOrigin ; успех ⇒ вина переносится на исходный хост ( DISPUTE_ORIGINATOR ); неудача ⇒ вина остается на перевозчике ( DISPUTE_CARRIER ).

Сильное доказательство

Когда Strong находится на связи (light_block не пуст), проверка является криптографической по отношению к закрепленному набору валидаторов:

  • Декодируйте байты как эквивалент LightBlock (blockoracle.Header).
  • Проверьте «chain_id», «height», «block_hash» на соответствие требованиям.
  • Убедитесь, что validators_hash соответствует корню Меркла закрепленного набора.
  • (Необязательно) Шаг 3b — сверка с набором участников для каждой эпохи.
  • Проверьте BlockID == hdr.BlockID .
  • Запустите VerifyCommit: каждая подпись фиксации передается закрепленному валидатору, дубликатов нет, накопленная мощность строго > 2/3 от общей суммы.
  • (Необязательно) Периодичность: h ≥ local_tip − max_lag_blocks else VALID_STALE .

13. Перенос, происхождение, атрибуция

Правила

  • Поля отправителя являются неизменяемыми для всех переходов. Оператор НЕ ДОЛЖЕН перезаписывать OriginatorSenderID или OriginatorTimestampMs.
  • D привязан к переносу. Перенос привязки с |H − local_aligned| > D НЕДЕЙСТВИТЕЛЬНО; Вместо этого оператор связи ДОЛЖЕН перейти на уровень «Сильный». (Это более строгая форма Strong_required.)
  • Подпись отправителя удалена по запросу. Исходящий запрос пользователя никогда не содержит поле 8. Хост доверяет запросу на основе правил свежести и частоты; криптографическое доказательство находится в кэшированном большом двоичном объекте пользователя.
  • Перенос без происхождения = источником является перевозчик. Если пользователь пересылает раздел с пустыми полями отправителя, оператор связи становится криптографическим подписывающим лицом претензии и принимает на себя любой спор ( DISPUTE_CARRIER ).

последняя_пропагированная дисциплина

HeightSyncPeerTips.ShouldPropagateTo(recipient, h) возвращает true, если h > last_propagated[recipient] . При успешной отправке MarkPropagated(recipient, h) поднимает верхнюю отметку. Для достижения кворума на позднем хосте требуется лестница строго возрастающей высоты (верная в производстве), а не три ленивых переноса на одном и том же H .

14. API подтверждения

Контракт

// devshard/heightsync/confirmation.go type ConfirmState int const ( ConfirmPending ConfirmState = iota ConfirmConfirmed ConfirmStale ) type ConfirmationView интерфейс { IsStrictlyConfirmed ( h uint64 ) ConfirmState }

Семантика:

  • подтверждено — h очистил настроенное правило подтверждения. Нижестоящие протоколы МОГУТ рассматривать (h, hash) как авторитетные.
  • в ожидании — у функции синхронизации высоты есть данные для h, но правило еще не очищено. Нижестоящие компании НЕ ДОЛЖНЫ выносить состязательные вердикты; cPoC возвращает неопределенный результат ( C6 ).
  • stale — h не может быть вычислен, поскольку оракул устарел/отключен. Нижестоящая компания считает вердикты неубедительными до тех пор, пока они не будут восстановлены.

Монотонность: однажды подтвержденная высота остается подтвержденной. ожидание → подтверждено — единственный переход вперед.

Правила подтверждения

Настраивается во время развертывания:

Развертывания PoC без Strong run (C-quorum). При развертывании производственного класса СЛЕДУЕТ выбрать (C-гибрид) после включения Strong.

Память подтверждений и обрезка

  • При приеме: обновить запись для каждого отправителя, если высота находится в окне.
  • При продвижении кончика: компактный ( max_height < Tip − W_conf или Observe_at Past F ).
  • Защита монотонности: сохраните небольшой набор подтвержденных_высот, чтобы обрезка никогда не уменьшала подтвержденную высоту.

Индивидуальный просмотр, а не глобальный

IsStrictlyConfirmed вычисляется по собственному кольцу и часам аудита вызывающего абонента. Два верификатора могут временно не согласиться (ожидание или подтверждение); Срезка на основе кворума cPoC допускает это.

15. Интеграция cPoC — полный API

Следующие API Go представляют собой стабильную поверхность, которую используют cPoC и финализация. Пути реализации указаны в скобках.

15.1 Дискретный предикат подтверждения

// На стороне пользователя (курьера): func (c * Transport. HTTPClient) ConfirmationView() heightsync. ConfirmationView // На стороне хоста (собственный оракул): func (s * Transport. Сервер) ConfirmationView() heightsync. Просмотр подтверждения

Оба предоставляют ConfirmationView.IsStrictlyConfirmed(h uint64) ConfirmState . cPoC §C6 / §C14 / §Вердикт, шаг 5, позвоните напрямую.

Пример использования (вердикт cPoC):

просмотр := сервер . ConfirmationView() переключение вида. IsStrictlyConfirmed (h) {case heightsync. ConfirmConfirmed: // регистр вердикта фиксации heightsync. ConfirmPending: возвращает InconclusivePendingHeight(h) в случае heightsync. ConfirmStale: return InconclusiveStaleOracle() }

15.2 Наблюдаемая высота (пульс курьера)

func ( c * транспорт. HTTPClient) ObservedHeightNow() (uint64, bool)

Возвращает (h, true), где h — самая высокая свежая подсказка в кэше одноранговых подсказок курьера; (0, ложь), когда нет свежего наконечника или синхронизация высоты не настроена. Используется пульсом cPoC C14 — ложный возврат означает «Неубедительно — нет свежей высоты».

15.3 Доказательства, оправдывающие вину (уровень спора)

// На стороне пользователя: создать подписанный BLOB-объект отправителя для (originator, h). func ( c * транспорт. HTTPClient) HeightSyncEvidenceFor (строка источника, h int64,) (blob, sig [] байт, ok bool)

Возвращается кэшем курьера ( HeightSyncPeerTips.OriginSignedBlobFor ). Можно проверить с помощью heightsync.VerifyOriginDetached(verifier, sec, blob, sig) без доступа к пользователю.

15.4 Доказательства сильной степени (сильный режим)

// Хост-сторона: вернуть кэшированный LightBlock для h, если он доступен. func(s *transport. Сервер) LightBlockFor (h int64) (доказательство [] байт, ок bool)

Когда ведомый получателя прошел мимо спорного H , спорный пакет может содержать обе половины:

  • Подписанный BLOB-объект отправителя из HeightSyncEvidenceFor (виновник).
  • LightBlock получателя из LightBlockFor (каноническая пара).

Верификатор ложного спора возвращает DISPUTE_ORIGINATOR, когда оба пройдены.

15.5 Семена холодного запуска (опция)

// Опция сервера: транспорт. WithHeightSyncSeedRPC ( true ) // Вызов клиента: func ( c * Transport. HTTPClient) SeedHeightSync (ctx context. Контекст) (uint64, bool, ошибка)

Согласие POST /sessions/:id/height-sync : хост возвращает принудительную привязку (подписанную отправителем). Курьер проверяет + кэширует его перед выдачей первого вывода — полезно для кратковременных сеансов, когда первый вывод не выполняется в ходе синхронизации.

15.6 Принудительный синхронный поворот

// Оператор/спор/триггер cPoC: состояние. SendMsgForceHeightSyncTurn ( триггерНонсе , slotsNum , причина , StrongRequired )

Открывает ActiveForcedTurn в следующем диффе; каждый конверт в [trigger, Trigger + slots_num − 1] ДОЛЖЕН быть Anchor (или Strong, если StrongRequired = true ).

15.7 Аудит и споры с потребителями

тип AuditRing интерфейс { List ( строка PeerID ) [] AnchorAttestation ListPeers () [] строка ConfirmationView () ConfirmationView } func ( c * Transport. HTTPClient) HeightSyncAuditRing () * heightsync. Функция AuditRing (s*transport. Сервер) HeightSyncAuditRing () * heightsync. АудитРинг

Используется потребителями споров/завершений, которым необходимы дословные подтверждения и история каждого узла.

16. Модель атаки

Каждая строка отображает действия противника на защиту протокола и на тестовый сценарий, который это доказывает (полный каталог в height-sync-tests.md (https://github.com/a-kuprin/gonka/blob/devshard-testenv/devshard/docs/height-sync-tests.md)).

Внешние противники:

  • Злоумышленник, который контролирует > 2/3 валидаторов основной сети — вне защиты этого протокола; то же, что и любое консенсусное предположение L1.
  • Злоумышленник, который отравляет локальный оракул блока хоста — оракул блока имеет собственный закрепленный верификатор набора валидаторов (blockoracle/verifier); синхронизация высоты не проверяется повторно.

17. Значения по умолчанию и конфигурация

Порядок чтения для участников

  • §6 — схема архитектуры. Создайте мысленную модель хост-производителя (собственный оракул, ветвь ответа на знаки) и пользователя-курьера (одноранговый кеш, носитель запроса), питающих один конвейер-получатель.
  • §8 — три режима синхронизации и диаграмма состояний.
  • §11 — блок-схема конвейера приемника; это нормативный раздел по несущей способности.
  • §12 — асимметричная модель подписи.
  • §14 + §15 — что на самом деле потребляет cPoC.
a-kuprin avatar
a-kuprinMaintainerMaintainer

Важным моментом в соответствии с этим предложением является то, что часть консенсуса выходит за рамки и должна быть здесь: Протокол финализации (https://github.com/gonka-ai/gonka/discussions/1369).

Это оптимистичный протокол — мы верим, пока высота находится в некоторой отметке от нашей известной высоты. Но мы всегда подписываемся хэшем блока отправителя, поэтому позже, когда мы получим ту же высоту от dapi, если мы увидим, что хеш блока отличается, мы можем начать спор, и создатель недействительных данных будет наказан.

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

shd avatar

Случайные комментарии, часть 1.

  • Я бы предложил подробно описать «неоптимистический путь». Насколько я понимаю, этот путь ведет к раунду консенсуса, но, возможно, это следует описать лучше. В частности, «Сильный (LightBlock + VerifyCommit)» — либо необходимы ссылки на протокол, либо должно быть предоставлено его описание. Существующее описание слишком схематично (по крайней мере, на мой посторонний взгляд).

Также я бы предложил более подробно описать использование height/block_hash: возможно, после этого станут очевидными некоторые улучшения алгоритма.

  • Несколько случайных вопросов и идей по поводу D (максимального интервала между соседними высотами):

... Мы доверяем высотам в будущем без дополнительных доказательств в сети, если высота близка к той, которая, как мы знаем, является текущей. Если между хостами существуют большие разногласия по высоте (height_in_the_future -known_height = |Δ| > D), мы используем полные данные из основной сети (хэш блока и подписи валидаторов) для проверки высоты...

Что, если разница во времени между одноразовыми номерами достаточно велика? Например, 1 минута? Это приведет к большим разногласиям и раунду консенсуса (даже несмотря на то, что нет реальной причины для спора: модель атаки 13, длительное время между блоками (подача тихая, кешированный совет все еще действителен)). Представьте, что запросы на вывод приходят с интервалом в 1 минуту: каждый будет сопровождаться консенсусом.

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

Здесь у нас могут быть следующие предложения:

а) (Может быть, это неправильно), но основное использование высоты — это обнаружение фазы PoC: если пользователь запрашивает вывод, но узел отвечает «отклонено из-за PoC». В этом случае пользователь немедленно отправляет запрос следующему узлу, и ответ приходит немедленно, и D важен. Таким образом, проверка на D может быть активирована только в некоторых случаях. В противном случае мы всегда доверяем прогрессу высоты, если только предыдущая высота не была раньше нашей.

б) Пользователь может указать текущее время в запросе - поэтому хост должен предоставить высоту блока, ближайшую к времени пользователя, возможно, +-1 слот. В случае, если время пользователя слишком отличается от времени хоста, можно активировать консенсус (сильная фаза): хосты отправляют всем текущее время пользователя и его собственное текущее время. В случае 5-секундного консенсуса (сопоставимого со скоростью консенсуса в основной сети) этот механизм может быть достаточно эффективным.

в) Адаптивные актуальные гарантии высоты. Пользователь делает пустые запросы каждые 5 секунд, он должен их выполнить (чтобы эти запросы получали последнюю высоту в нужные промежутки времени, а изменения высоты всегда были либо 0, либо 1) -- или выполняет консенсус в случае длительной паузы.

Консенсус может быть очень долгим по сравнению с этими пустыми сообщениями. Эти сообщения могут передаваться в отдельном циклическом переборе, то есть не влиять на следующий узел вывода. Они могут остановиться после (N^2/2) пустых запросов, то есть остановиться прямо перед тем, как цена поддержки работоспособности станет больше, чем цена консенсуса после восстановления.

  • Источник случайности: а) предлагаемый метод оставляет некоторое пространство для манипуляций (поскольку разница высот < D дает возможность выбора наилучшего хеша). б) в случае хоста противника И пользователя эта возможность становится гарантией (пользователь и хост вместе всегда могут выбрать подходящую задержку для получения необходимого хэша - например, для выбора правильного следующего узла) в) поэтому здесь могут быть предпочтительны схемы фиксации: если хост и пользователь из «разных команд» d) в случае, если хост И пользователь вместе являются противниками, могут быть предложены дополнительные подходы, точная формулировка выходит за рамки комментария.
akup avatar
akupMaintainerMaintainer
2026-07-01

Я думаю, мы начнем еще одну дискуссию в зависимости от источника случайности.

хэш блока может быть источником случайности и вполне естественен, когда у нас есть протокол синхронизации высоты (который нам в любом случае нужен для обработки cPoC на devshard). Но источник случайности в стиле Педерсона, на мой взгляд, очень элегантен. Пожалуйста, запишите это предложение

shd avatar

Важная информация о D: элементарный анализ показывает, что на границе периода неактивности 30 секунд/1 минуты требуется значительное увеличение количества раундов консенсуса.

Очень приблизительные фактические данные: в эпоху 263 было 590 000 запросов на вывод на 44 пользователя, что дает T = 0,15 запросов в секунду. Мы можем считать (в качестве первого предположения), что запросы на вывод следуют распределению Пуассона, это дает e^(-0,15 * 60) = 0,0001 вероятность 60-секундного бездействия; и e^(-0,15 * 30) = 0,01 вероятность 30-секундного бездействия.

Конечно, распределение другое (запросы обычно соответствуют какому-то процессу вывода, то есть они не являются независимыми), и точные цифры будут другими. Но D может повлиять на производительность весьма необычным образом, если умеренная пауза в выводах приведет к новому раунду консенсуса.

akup avatar
akupMaintainerMaintainer
2026-07-01

D здесь не связан с одноразовыми номерами/блоками, он связан с высотой блока основной сети.

Если мы находимся на разнице высот > D, нам следует запустить раунд консенсуса, и время приращения блока в основной сети довольно стабильно, около 5 секунд, но при высокой нагрузке зимой мы наблюдали медленное построение блоков, когда один блок увеличивался примерно за 30-45 секунд.

shd avatar
shdMaintainerMaintainer
2026-06-22

Вопрос: Существует вектор атаки, модель атаки 13, Длительное время между блоками (отключение канала, кэшированная подсказка все еще действительна). Однако есть ли какая-либо конкретная атака/случай длительного бездействия пользователя?

akup avatar
akupMaintainerMaintainer

Это было описано отдельно в предложении протокола cPoC. На него есть ссылка в этом документе в репозиториях GitHub, но я только что опубликовал его как обсуждение: # 1384 (https://github.com/gonka-ai/gonka/discussions/1384).

Есть случаи для обработки параграфа и особенно C14 — стратегическая задержка при низкой нагрузке (подавление пульса разработчика), обсуждающая именно это долгое бездействие пользователя.

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

shd avatar

Генерация случайных чисел

Генерация случайных чисел из хеша блокчейна самого последнего блока основной сети имеет много преимуществ, однако имеет следующие проблемы:

  • он не позволяет генерировать номер "на месте" --- требует задержки до прибытия нового блока основной сети.

он не позволяет генерировать номер "на месте" --- требует задержки до прибытия нового блока основной сети.

  • задержки синхронизации: генерирующая сторона имеет некоторую возможность выбирать из нескольких заголовков (в диапазоне D последовательных заголовков) и, следовательно, имеет некоторое влияние на количество.

задержки синхронизации: генерирующая сторона имеет некоторую возможность выбирать из нескольких заголовков (в диапазоне D последовательных заголовков) и, следовательно, имеет некоторое влияние на количество.

Обе проблемы можно решить, но за это приходится платить, поэтому рекомендуется рассмотреть разные подходы.

Альтернативный подход

В качестве простого альтернативного подхода мы предлагаем использовать стандартную схему обязательств. Возьмем какую-нибудь надежную хеш-функцию (например, sha256) --- назовем ее F(x), где x — двоичная строка.

Тогда генерация случайных чисел (для пользователя U и хоста H) может следовать следующей схеме:

  • Каждая сторона (U и H) генерирует одно случайное число --- пусть это будут R_U и R_H. Это должно быть длинное число, например. 128 или 256 бит, чтобы предотвратить угадывание числа.

Каждая сторона (U и H) генерирует одно случайное число --- пусть это будут R_U и R_H. Это должно быть длинное число, например. 128 или 256 бит, чтобы предотвратить угадывание числа.

  • Затем стороны обмениваются хэшами числа --- H отправляет F(R_H) в U, а U отправляет F(R_U) в H.

Затем стороны обмениваются хэшами числа --- H отправляет F(R_H) в U, а U отправляет F(R_U) в H.

  • После обмена значениями стороны обмениваются исходными случайными числами: таким образом, каждая из сторон знает R_U, R_H, F(R_U) и F(R_H).

После обмена значениями стороны обмениваются исходными случайными числами: таким образом, каждая из сторон знает R_U, R_H, F(R_U) и F(R_H).

  • Значение Xor(R_U,R_H) является желаемым случайным значением.

Значение Xor(R_U,R_H) является желаемым случайным значением.

Каковы свойства этого протокола, анализ его безопасности

  • Если числа R_U и R_H сгенерированы без знания друг друга, и хотя бы одна из сторон сгенерировала их случайным образом, то результирующее значение также является случайным.

Если числа R_U и R_H сгенерированы без знания друг друга, и хотя бы одна из сторон сгенерировала их случайным образом, то результирующее значение также является случайным.

  • Значения F(R_U) и F(R_H) не раскрывают никакой информации о R_U, R_H, хотя в случае коротких чисел возможен перебор (поэтому у нас есть требование 128 или 256 бит).

Значения F(R_U) и F(R_H) не раскрывают никакой информации о R_U, R_H, хотя в случае коротких чисел возможен перебор (поэтому у нас есть требование 128 или 256 бит).

  • Стороны не могут изменить свое решение после обмена хэшами.

Стороны не могут изменить свое решение после обмена хэшами.

Хэш-коллизии

Sha256 по-прежнему считается устойчивым к коллизиям, однако для полноты текста необходимо отметить, что эту схему можно сделать более устойчивой к таким атакам, даже если такие коллизии будут обнаружены (или используется более слабая хеш-функция).

Например, у вас есть пара чисел, которые имеют один и тот же хэш, но разные значения: A,B такие, что F(A) = F(B). Таким образом, в момент раскрытия чисел у противника может быть выбор (либо А, либо Б), что может привести к некоторому контролю над полученным случайным числом.

Чтобы предотвратить это, можно добавить к схеме обмен одноразовыми номерами:

  • Стороны обмениваются двумя случайными числами (N_U и N_U соответственно), и случайные числа, генерируемые на шаге 1, должны включать эти числа в качестве префиксов: R'_U = N_H++R_U и R'_H = N_U++R_H, где ++ — объединение двоичных строк.

Эта модификация должна запретить другой стороне использовать ранее вычисленные коллизии в хэш-функциях. Необходимость такого усложнения генерации случайности дискуссионна.

Общий анализ

  • Эта схема безопасна, устойчива к враждебному поведению сторон и генерирует хороший рандом, если в этом заинтересована хотя бы одна из сторон.

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

  • Эту схему можно активировать по требованию в любой момент, и она довольно дешевая. Правда, для этого требуется 2 элементарных сообщения (или 3, в более безопасном варианте) от каждой стороны.

Эту схему можно активировать по требованию в любой момент, и она довольно дешевая. Правда, для этого требуется 2 элементарных сообщения (или 3, в более безопасном варианте) от каждой стороны.

  • Однако он не сопротивляется совместной случайной генерации: когда и U, и H вместе пытаются подделать желаемое число. Хэш-схема блокчейна также в некоторой степени имеет эту проблему.

Однако он не сопротивляется совместной случайной генерации: когда и U, и H вместе пытаются подделать желаемое число. Хэш-схема блокчейна также в некоторой степени имеет эту проблему.

Проверка вывода, устойчивая к сотрудничеству (проект предложения)

Необходимость многохостовых девшардов продиктована необходимостью распределить контроль над процессом инференса, сделать его прозрачным и честным. Однако совместное поведение пользователя и некоторых хостов может повредить гарантиям. В частности, такое сотрудничество может легко поручить проверку скомпрометированных выводов враждебным хостам, тем самым повредив всю схему.

Чтобы этого не произошло, предлагается следующая идея.

  • Каждый хост должен хранить последние 32 (по размеру devshard) вывода, готовые для будущей проверки. Фактический проверяющий и фактический вывод, который будет проверен, на данный момент никому не известны.

Каждый хост должен хранить последние 32 (по размеру devshard) вывода, готовые для будущей проверки. Фактический проверяющий и фактический вывод, который будет проверен, на данный момент никому не известны.

  • Хост и верификатор для данного вывода определяются путем генерации случайных чисел.

Хост и верификатор для данного вывода определяются путем генерации случайных чисел.

  • Затем верификатор запрашивает у хоста вывода его прошлую задачу (одну из 32) и выполняет необходимую проверку. Если случайное число не всегда генерируется противниками, а хотя бы иногда (фактически в 2/3 случаев) действительно случайно, это дает хорошую гарантию от мошенничества.

Затем верификатор запрашивает у хоста вывода его прошлую задачу (одну из 32) и выполняет необходимую проверку. Если случайное число не всегда генерируется противниками, а хотя бы иногда (фактически в 2/3 случаев) действительно случайно, это дает хорошую гарантию от мошенничества.

Эта идея не решает всех проблем с честностью пользователя и хостов, но дает дополнительный уровень защиты.

Главный вопрос здесь — как генерировать случайные числа. Текст ниже объясняет это.

Генерация случайных чисел

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

Представьте себе, пользователь U с хостом H генерирует число, и это число определяет верификатора (и они злоупотребляют протоколом, чтобы выбрать верификатора из своей команды). Это приводит нас к выводу, что число должно генерироваться всеми остальными хостами в devshard. Например, это может быть последовательная генерация: для первого вывода H_0 число генерируется U и H_1. Для второго вывода H_0 — по U и H_2 и так далее. Таким образом, после 32 раундов (или 32 выводов каждого хоста) по крайней мере 20 из них будут случайно выбраны несотрудничающей стороной.

Итак, у нас должно быть два циклических цикла: цикл вывода (всем узлам предлагается сделать выводы последовательно, чтобы минимизировать пространство для затенения узлов) и цикл проверки.

Конкретные детали еще предстоит определить, но основная идея в том, что этот цикл должен сознательно идти с разной скоростью (по сравнению с циклом вывода). Чтобы в течение одного цикла вывода (чтобы вывод, начиная с H_0, возвращался обратно в H_0), хосты цикла проверки должны быть сдвинуты (verificatoin.H_0 с inference.H0 -- они начинаются на одном и том же хосте, но в момент возврата обратно в H_0 проверка должна быть в H_k с k/= 0).

Так как в сети 2/3 честных хостов, то за 2 из 3 циклов хост окажется в паре с хостом, которого нет в его команде противника. И после 11 циклов у каждого хоста будет хотя бы одна проверка, контролируемая независимым хостом.

Цикл вывода может быть облегченным (то есть он может не иметь независимого блокчейна), и все транзакции могут быть добавлены в одну и ту же последовательность различий; эти два цикла могут быть переплетены в одном блокчейне devshard детерменистическим образом.

Кроме того, этот цикл проверки можно использовать для распространения высоты блока (чтобы избежать частого вызова консенсуса).

a-kuprin avatar
a-kuprinMaintainerMaintainer
2026-07-02

Я думаю, что этот стиль обязательства Педерсона для источника случайности является лучшим вариантом для финализации (https://github.com/gonka-ai/gonka/discussions/1369) выбора сборщика для обязательства.

В любом случае нам следует вернуться к протоколу финализации, поскольку BLS очень хорошо подходит для него, как мы уже обсуждали.

Русский перевод
alexanderkuprin avatar
alexanderkuprinАвтор

Протокол синхронизации по высоте

Конверты пользователя ↔ хоста содержат дополнительный HeightSyncSection, который подтверждает тройку основной сети (height,block_hash,block_timestamp). Этот раздел является единственным вводом для межузлового выравнивания времени, принятия решений о тайм-ауте и предиката IsStrictlyConfirmed(h), который выносит вердикт нисходящим протоколам (cPoC, финализация). Block_hash может быть источником детерменистической случайности, которая заранее неизвестна, может использоваться VALIDATION_PROTOCOL_PROPOSAL.md (https://github.com/a-kuprin/gonka/blob/devshard-testenv/devshard/docs/proposals/VALIDATION_PROTOCOL_PROPOSAL.md). Существуют и другие варианты источника случайности, которые необходимо обсудить.

block_timestamp может быть детерминированным временем, которое будет использоваться в devshard для таймаутов.

Этот документ является канонической спецификацией одной версии. Реализация в дереве соответствует этому документу; в каталоге тестов (height-sync-tests.md (https://github.com/a-kuprin/gonka/blob/devshard-testenv/devshard/docs/height-sync-tests.md)) указано, что уже доказано и что планируется.

Связанные документы: height-sync-tests.md (https://github.com/a-kuprin/gonka/blob/devshard-testenv/devshard/docs/height-sync-tests.md) (каталог тестов — реализован и запланирован), протокол CPOC для devshard (https://github.com/gonka-ai/gonka/discussions/1384), протокол финализации (https://github.com/gonka-ai/gonka/discussions/1369), VALIDATION_PROTOCOL_PROPOSAL.md (https://github.com/a-kuprin/gonka/blob/devshard-testenv/devshard/docs/proposals/VALIDATION_PROTOCOL_PROPOSAL.md).

Оглавление

Резюме (#1-резюме)

Проблема (№2-проблема)

Общий обзор протокола (#3-высокоуровневый обзор протокола)

Голы (#4-голы)

Глоссарий (#5-глоссарий)

Обзор архитектуры (#6-обзор архитектуры)

Формат провода (#7-формат провода)

Режимы синхронизации (Omit / Anchor / Strong) (#8-sync-modes-omit--anchor--strong)

Каденция (синхронизация поворотов, K, slots_num, принудительные повороты) (#9-каденция)

Правила производителя (#10-producer-rules)

Приемный конвейер (№ 11-приемник-конвейер)

Модель доверия и подписи (#12-trust-model-and-signatures)

Перенос, происхождение, атрибуция (# 13-перенос-происхождение-атрибуция)

API подтверждения ( IsStrictlyConfirmed ) (#14-confirmation-api)

Интеграция cPoC — полный API (#15-cpoc-integration-api)

Модель атаки и меры по ее устранению (#16-модель атаки)

Значения по умолчанию и конфигурация (#17-defaults-and-configuration)

Статус и вехи (#18-статус-и-вехи)

1. Резюме

Трафик вывода пользователь-хост содержит тело HTTP, состоящее из двух разделов:

  • HeightSyncSection — дополнительная аттестация основной сети: подписанная пара (mainnet_height, mainnet_block_hash), а также метаданные кадрирования и происхождения.
  • message_body — полезная нагрузка приложения (непрозрачна для синхронизации по высоте).

Раздел 1 выдается только при необходимости:

  • Синхронизация хода — стандартная каденция: каждые K одноразовых номеров, окно последовательных одноразовых номеров slots_num несет в себе Anchor; между ними, Омит.
  • Принудительный поворот синхронизации — MsgForceHeightSyncTurn в любой момент открывает диапазон привязки slot_num (открывается спор cPoC, принудительное вмешательство оператора).
  • |Δ| > D — когда заявленная высота отправителя отличается от выровненной высоты получателя более чем на D, отправитель ДОЛЖЕН использовать Strong ( LightBlock + VerifyCommit ); в противном случае получатель отклоняет.

Хосты подписывают якоря ответной ветви своим ключом подписывающего лица secp256k1; пользователи-курьеры переносят эти подписанные BLOB-объекты вперед, проверяя при приеме и используя их в качестве доказательства невиновности по требованию. Якорям ветки запроса доверяют хосты (без встроенной подписи) — в случае спора пользователь доказывает происхождение позже.

Одиночный предикат IsStrictlyConfirmed(h) предоставляет нижестоящим потребителям дискретный ответ {подтвержденный, ожидающий, устаревший}.

2. Проблема

В devshard нам нужен источник высоты и случайности основной сети, который заранее неизвестен, но является детерминированным, чтобы заставить все хосты и пользователя согласиться.

Каждый хост имеет свой собственный последний (высота, block_hash) оракул (цепочка вывода grpc) и подписи валидаторов цепочки вывода. Но хосты должны согласиться с тем, что любая высота, указанная в протоколе, действительно является последней. Таким образом, в этом документе должен быть указан протокол синхронизации по высоте.

3. Общий обзор протокола

Поскольку devshard рассчитан на высокую пропускную способность, мы стремимся свести к минимуму дополнительные данные, передаваемые в сообщениях, и свести к минимуму проверки подписей Minnet. Также мы минимизируем сплетни, которые должны возникать только в случае споров и урегулирования, чтобы минимизировать трафик (поскольку один хост может находиться во многих devshard'ах).

Итак, основные дизайнерские решения приняты:

  • Синхронизация высоты происходит не при каждом nonce, а только в специализированных окнах, где мы добавляем только к запросу/ответу (height,block_hash) и подпись отправителя (хоста, отвечающего на запрос), чтобы доказать происхождение этих данных для возможных споров. Пользователь переносит (высоту, block_hash) при следующих запросах для распространения этих данных на другие хосты.
  • Мы доверяем высотам в будущем без дополнительных доказательств в сети, если высота близка к той, которая, как мы знаем, является текущей. Если между хостами существуют большие разногласия по высоте ( height_in_the_future -known_height = |Δ| > D ), мы используем полные данные из основной сети (хэш блока и подписи валидаторов) для проверки высоты.
  • Если мы обнаружим, что ранее предоставленная каким-либо хостом высота не соответствует оракулу block_hash, мы начинаем спор.

В результате мы предоставляем API для devshardd и devshardctl, который выдает последнюю высоту, хэш блока и информацию, если с этим согласны большинство участников сети devshard.

4. Цели

  • Дешевое периодическое выравнивание — Anchor (без LightBlock ) по расписанию синхронизации позволяет каждому хосту видеть время основной сети в ограниченном окне без накладных расходов на проверку каждого сообщения.
  • Сильная эскалация разногласий — однажды |Δ| > D или финализация требует этого, доказательство с привязкой к кворуму валидатора ( LightBlock ) является обязательным.
  • Происхождение и атрибуция — каждый кэшированный (H, хеш) можно проследить до подписи исходного хоста; операторы связи не могут быть обвинены в пересылке подписанного заявления злонамеренного хоста, а операторы связи, которые раскрывают происхождение, становятся источником шифрования.
  • Устойчивость к повторному воспроизведению — бюджет актуальности F по временной метке отправителя + учет последней распространенной информации для каждого получателя не позволяет оператору повторно использовать устаревшие или уже доставленные чаевые.
  • Контракт подтверждения для последующих потребителей — дискретный предикат IsStrictlyConfirmed(h) ∈ {confirmed, pending, stale}, поэтому cPoC/финализация не изобретают собственную логику кворума.
  • Развертывание только через курьера — пользователи, у которых нет собственных последователей в основной сети, по-прежнему могут передавать подписанные подсказки хоста между хостами в циклическом переборе и подтверждении охвата (C-quorum).

5. Глоссарий

6. Обзор архитектуры

блок-схема LR основная сеть подграфа [основная сеть Gonka] BC[Консенсус CometBFT] end BC -- "заголовки блоков + фиксации" --> HSD[heightsyncd /blockoracle] хост подграфа [время выполнения хоста] HSD --> SCH_H[AnchorScheduler<br/>локальный источник Oracle] SCH_H --> TX_H[transport.Server<br/>подписывает ответную часть] TX_H -- «HeightSyncSection<br/>входящий запрос» --> RX_H[Конвейер получателя<br/>D-диапазон, актуальность, классификация] RX_H --> AUD_H[AuditRing + ConfirmationIndex] AUD_H -- «IsStrictlyConfirmed» --> CPOC_H[потребитель cPoC] конечный пользователь подграфа [Courier user / devshardctl] TIPS[HeightSyncPeerTips<br/>дословно подписанные BLOB-объекты] --> SCH_U[AnchorScheduler<br/>источник одноранговых подсказок] SCH_U --> TX_U[transport.HTTPClient<br/>Перенести, снять подпись по запросу] TX_U -- "входящий ответ<br/>проверить + кэш" --> RX_U[Проверить ответ Anchor<br/>RecordOriginWithBlob] RX_U --> TIPS TIPS -- "IsStrictlyConfirmed" --> CPOC_U[потребитель cPoC] end TX_U -- "ветвь запроса<br/>HeightSyncSection" --> RX_H TX_H -- "ветвь ответа<br/>HeightSyncSection (подписанная)" --> RX_U

Ключевые инварианты:

  • У каждого хоста есть собственный подписчик в основной сети (heightsyncd /blockoracle); это канонический источник local_aligned.
  • У пользователя нет подписчиков (режим курьера); он извлекает local_aligned из проверенного однорангового кэша, заполненного подписанными ответами хоста.
  • HeightSyncSection — единственная поверхность проводов, связанная с основной сетью, на конвертах вывода; приемный трубопровод – однопоточный.

7. Формат провода

HeightSyncSection передается как поле protobuf в конверте вывода и зеркально отображается в формате JSON для инструментов. Номера полей стабильны.

Примечания:

  • Деградированный якорь (тихая подача). Если локальный оракул не получил новый блок в StaleAfter, но Latest() по-прежнему возвращает кэшированный заголовок, хосты выдают обычный Anchor (поля 1–8) плюс поле 10. Это позволяет избежать реакции синхронизации-поворота. Пропустить во время длинных промежутков между блоками; консенсус среди хостов по-прежнему исправляет меньшинство устаревшим советом. Пропустить остается обязательным, если нет кэшированной подсказки (канал никогда не запускался), происходит сбой Latest() (канал недоступен) или кеш одноранговых подсказок курьера пуст.
  • Подписи, привязанные к направлению. Поле 8 устанавливается хостами только в ответах. Carry() очищает поле 8 перед отправкой части запроса; Проверка входящего запроса не требует встроенной подписи.
  • Канонический ввод подписи. CanonicalOriginBytes(sec) = "heightsync.origin.v1" || прото.Маршал(поля 1..7) . Поле 8 не является частью входных данных для подписи.
  • Резервирование на уровне проводов. origin_attestation (встроенный исходный объект) зарезервирован для будущих встроенных развертываний; текущий протокол использует асимметричную модель (подписанный ответ, доверенный запрос, оправдание по требованию).

Зеркало JSON:

{ "height_sync": { "proof_type": "height-anchor-v1", "mainnet_height": 42, "mainnet_block_hash_hex": "abc... ", "timestamp_unix_ms": 1700000000000, "направление": "ответ", "originator_sender_id": " gonka1host... ", "originator_timestamp_unix_ms": 1700000000000, "sender_signature": "base64... " , "light_block" : " base64... ", "tip_stale_after_ms": 12000 } }

( Tip_stale_after_ms опускается, если кэшированная подсказка свежая.)

8. Режимы синхронизации (пропустить/привязать/сильный)

stateDiagram-v2 направление LR [*] --> Пропустить Пропустить --> Привязка: одноразовый номер при синхронном повороте/принудительном повороте/ленивом переносе Привязка --> Пропустить: следующий одноразовый номер за пределами окна Привязка --> Строгая: \|H − local_aligned\| > D ИЛИ принудительное (StrongRequired) Сильное --> Привязка: партнеры перестроены, снова внутри D Привязка --> Привязка: частота шагов на следующем ходу Сильная --> Сильная: неподвижно > D

Периодическое выравнивание использует только Anchor. Сильный – это не шаг по умолчанию, а путь разногласий/споров.

Тихий канал против мертвого канала (хосты):

9. Каденс

Синхронно-поворотные окна

Для направления сеанса для исходящего nonce n:

  • Начальный ход синхронизации: 1 ≤ n ≤ slots_num → Anchor (или Strong, см. §10).
  • Периодические циклы синхронизации: для каждого i ≥ 1 i·K ≤ n ≤ i·K + slot_num − 1 → Anchor.
  • Все остальные одноразовые номера → Опустить, если не применяется принудительная директива или отложенный перенос.

Ограничение: K ≥ slot_num, чтобы окна никогда не перекрывались.

Gantt title Частота синхро-поворотов (K=8, slot_num=4) dateFormat X axisFormat %s раздел Каденция Начальный синхро-поворот (Привязка) :a1, 1, 4 Пропустить :a2, 5, 7 Периодический синхро-поворот 1 :a3, 8, 11 Пропустить :a4, 12, 15 Периодический синхро-поворот 2 :a5, 16, 19

Принудительный поворот синхронизации

MsgForceHeightSyncTurn(trigger_nonce, slots_num, Reason,strong_required?) открывает диапазон ActiveForcedTurn{start, end}:

  • Оба направления ДОЛЖНЫ излучать Anchor для каждого конверта в [start, end] . Пропустить внутри принудительного поворота НЕДЕЙСТВИТЕЛЬНО.
  • Strong_required = true обновляет окно до Strong.
  • Вторая директива, пока ход активен, молча игнорируется.
  • Принудительное окно, которое перекрывает следующее окно каденции, поглощает его (нет двойной привязки на границе).
  • После n > end каденция возобновляется по стандартному правилу.

Отложенный перенос (курьерские развертывания)

За пределами любого окна синхронизации-поворота пользователь-курьер МОЖЕТ выдать Anchor на этапе запроса iff:

  • Одноранговый кеш содержит новый раздел отправителя ( MaxFresh(now, F) возвращает ненулевое значение).
  • кэшированная_максимальная_высота > Last_propagated[получатель] .

Получатель классифицирует это как VALID_LAZY_ANCHOR (тег аудита lazy ); он не открывает обязательство синхронизации-поворота.

10. Правила продюсера

Хосты (есть собственный оракул)

  • При каждом исходящем ответе: обратитесь к местному оракулу; если применяется синхронный или принудительный поворот, выдайте Anchor с OriginatorSenderID = host_address , OriginatorTimestampMs = now и подпишите раздел (поле 8). Если оракул молчит (нет нового блока внутри StaleAfter), но существует кэшированный совет, все равно выдайте этот Anchor и установите для Tip_stale_after_ms возраст последнего принятого блока (поле 10 устанавливается после подписания полей ввода 1–7). Пропускайте только в том случае, если нет полезной кэшированной подсказки или происходит сбой Latest().
  • Если установлен Force.StrongRequired ИЛИ значение Peer_aligned_height получателя отличается от локального наконечника на > D: создайте Strong, присоединив кэшированный LightBlock для H (поле 9).
  • По входящим запросам: ничего не подписывайте; классифицировать через приемный конвейер (§11).

Пользователь-курьер (нет собственного оракула)

  • Поддерживайте HeightSyncPeerTips с ключом OriginatorSenderID .
  • Проверка ответов хоста при приеме ( VerifyOrigin ); в случае неудачи отбросьте подсказку и увеличьте origin_sig_invalid_total.
  • По исходящим запросам: обратитесь к планировщику; ленивый перенос только тогда, когда в кеше есть подсказка, еще не переданная получателю. Перед отправкой очистите поле 8 (sender_signature).
  • Производитель никогда не устанавливает OriginatorSenderID = user_address; это поле отражает хост, подписавший кэшированный большой двоичный объект.

11. Ресиверный конвейер

блок-схема TD A[конверт прибывает] --> B{HeightSyncSection<br/>присутствует?} B -- нет --> O{nonce в синхронном повороте /<br/>активный принудительный поворот?} O -- да --> O1[INVALID<br/>sync_turn_anchor_missing] O -- нет --> O2[VALID_OMIT] B -- да --> C{Привязка или сильная?} C -- Привязка --> D {"|H − local_aligned| > D?"} D -- да --> D1[НЕВЕРНО<br/>strong_required] D -- нет --> E{перенести<br/>набор исходного источника?} E -- да --> F{оригинатор в F?} F -- нет --> F1[НЕВЕРНО<br/>stale_required] F -- да --> G[классифицировать каденцию / ленивый<br/>по nonce vs синхронизировать поворот] E -- нет --> G C -- Strong --> H[StrongVerifier.VerifyLightBlock] H -- ok --> I[VALID_STRONG] H -- неудачно --> H1[INVALID<br/>strong_proof_invalid] G --> J{блок H локальный И<br/>хеш совпадает?} J -- да/совпадение --> K[VALID_ANCHOR или<br/>VALID_LAZY_ANCHOR] J -- нет/local-missing --> L[поставить в очередь отложенную проверку] J -- локальное И несоответствие --> M{присутствует отправитель?} M -- да --> M1[DISPUTE_ORIGINATOR] M -- нет --> M2[DISPUTE_CARRIER]

Нормативные шаги для конверта без пропуска:

  • Парсинг + кадрирование (прото/JSON).
  • Сначала проверьте вынужденный поворот. Если ActiveForcedTurn[start..end] установлен и start ≤ nonce ≤ end, конверт ДОЛЖЕН быть Anchor (или Strong, если StrongRequired). Пропустить ⇒ НЕДЕЙСТВИТЕЛЬНО.
  • Группа Д. Еслиproof_type == "height-anchor-v1" и |H − local_aligned| > D: НЕВЕРНО (strong_required).
  • Сильный путь. Еслиproof_type == "cometbft-light-block-v1": запустите StrongVerifier.VerifyLightBlock (идентификатор цепочки, заголовок против утверждений, validators_hash, необязательный шаг 3b с привязкой к эпохе, BlockID, фиксация > 2/3); неудача ⇒ НЕДЕЙСТВИТЕЛЬНО (strong_proof_invalid).
  • Наличие оригинатора и свежесть. Если OriginatorSenderID != "" : Если now_ms − OriginatorTimestampMs > F ⇒ НЕДЕЙСТВИТЕЛЬНО (stale_origin); доверие аудита = TrustDisputeCarrier . Остальное продолжайте.
  • Если now_ms – OriginatorTimestampMs > F ⇒ НЕДЕЙСТВИТЕЛЬНО (stale_origin); доверие аудита = TrustDisputeCarrier .
  • Остальное продолжайте.
  • Каденция/ленивая классификация. Внутренняя синхронизация-поворот (каденция/начальная/принудительная): VALID_ANCHOR (каденция тега). Внешняя синхронизация-поворот + присутствует отправитель (курьер): VALID_LAZY_ANCHOR (тег lazy ). Внешняя синхронизация + отправитель отсутствует + Привязка: самоаттестация устаревшего хоста; VALID_ANCHOR .
  • Внутренняя синхронизация-поворот (каденция/начальная/принудительная): VALID_ANCHOR (каденция тега).
  • Внешняя синхронизация-поворот + присутствует отправитель (курьер): VALID_LAZY_ANCHOR (тег lazy ).
  • Внешняя синхронизация + отправитель отсутствует + Привязка: самоаттестация устаревшего хоста; VALID_ANCHOR .
  • Согласование местного оракула. Если блок H локальный и хэш совпадает → подтверждается немедленно; фид ConfirmationIndex. Если H еще не является локальным → поставить в очередь отложенную проверку по (оригинатор, H, хеш); не увеличивайте height_seen_max. Если H является локальным и хеш-код отличается → DISPUTE_ORIGINATOR (присутствуют метаданные отправителя) или DISPUTE_CARRIER (отсутствует отправитель или подпись не удалась); сохранить оскорбительный подписанный BLOB-объект.
  • Если блок H локальный и хэш совпадает → подтверждается немедленно; фид ConfirmationIndex.
  • Если H еще не является локальным → поставить в очередь отложенную проверку по (оригинатор, H, хеш); не увеличивайте height_seen_max.
  • Если H является локальным и хеш-код отличается → DISPUTE_ORIGINATOR (присутствуют метаданные отправителя) или DISPUTE_CARRIER (отсутствует отправитель или подпись не удалась); сохранить оскорбительный подписанный BLOB-объект.
  • Аудит + метрики. Добавьте AnchorAttestation (с Tag , Trust , OriginatorSenderID , OriginSignedBlobAvailable ) к одноранговому кольцу; выдавать счетчики.
  • Обработать message_body, если оно не INVALID.

Классы результатов

12. Модель доверия и подписи

Протокол асимметричен: ответы подписываются, запросы доверяются, оправдание происходит по требованию.

Участник SequenceDiagram U как пользователь (курьер) Участник H как хост A Участник H2 как хост B U->>H: запрос (участок запроса, без подписи) H->>U: ответ Привязка [подписанная хостом A] Примечание над U: VerifyOrigin OK<br/>RecordOriginWithBlob(host_A, H, blob, sig) U->>H2: привязка запроса (перенос вперед, без встроенной подписи) Примечание через H2: доверяет несущему<br/>применяется шлюз свежести F. Примечание над H2: позже ведомый продвигается<br/>сравнивает хэш с каноническим H2 -->>U: DEFERRED_FAIL? открытый спор против пользователя U->>U: HeightSyncEvidenceFor(host_A, H) → blob + sig U-->>H2: Signed_blob доказывает отправителя = хост A<br/>⇒ DISPUTE_ORIGINATOR против хоста A

Этап ответа (хост → пользователь)

  • Хост заполняет OriginatorSenderID, OriginatorTimestampMs, создает CanonicalOriginBytes (поля 1–7 + домен heightsync.origin.v1).
  • Подписывается ключом secp256k1 хоста, устанавливает поле 8.
  • Пользователь проверяет поле 8 при приеме. Ошибка ⇒ удаление, нет кэша, нет распространения; origin_sig_invalid_total увеличивается.
  • В случае успеха: RecordOriginWithBlob(originator, sec, blob, sig) .

Ветка запроса (пользователь → хост)

  • Перенос копирует поля отправителя (6, 7) из кэшированного большого двоичного объекта.
  • Carry() удаляет поле 8 перед отправкой.
  • Хост принимает раздел, соответствующий конвейеру-получателю (§11). Никакая встроенная подпись не требуется и не проверяется.

Оправдание

Если позже хост открывает спор против пользователя-перевозчика, пользователь вызывает HTTPClient.HeightSyncEvidenceFor(originator, H) для создания кэшированного (blob, sig) . Средство проверки спора повторно запускает VerifyOrigin ; успех ⇒ вина переносится на исходный хост ( DISPUTE_ORIGINATOR ); неудача ⇒ вина остается на перевозчике ( DISPUTE_CARRIER ).

Сильное доказательство

Когда Strong находится на связи (light_block не пуст), проверка является криптографической по отношению к закрепленному набору валидаторов:

  • Декодируйте байты как эквивалент LightBlock (blockoracle.Header).
  • Проверьте «chain_id», «height», «block_hash» на соответствие требованиям.
  • Убедитесь, что validators_hash соответствует корню Меркла закрепленного набора.
  • (Необязательно) Шаг 3b — сверка с набором участников для каждой эпохи.
  • Проверьте BlockID == hdr.BlockID .
  • Запустите VerifyCommit: каждая подпись фиксации передается закрепленному валидатору, дубликатов нет, накопленная мощность строго > 2/3 от общей суммы.
  • (Необязательно) Периодичность: h ≥ local_tip − max_lag_blocks else VALID_STALE .

13. Перенос, происхождение, атрибуция

Правила

  • Поля отправителя являются неизменяемыми для всех переходов. Оператор НЕ ДОЛЖЕН перезаписывать OriginatorSenderID или OriginatorTimestampMs.
  • D привязан к переносу. Перенос привязки с |H − local_aligned| > D НЕДЕЙСТВИТЕЛЬНО; Вместо этого оператор связи ДОЛЖЕН перейти на уровень «Сильный». (Это более строгая форма Strong_required.)
  • Подпись отправителя удалена по запросу. Исходящий запрос пользователя никогда не содержит поле 8. Хост доверяет запросу на основе правил свежести и частоты; криптографическое доказательство находится в кэшированном большом двоичном объекте пользователя.
  • Перенос без происхождения = источником является перевозчик. Если пользователь пересылает раздел с пустыми полями отправителя, оператор связи становится криптографическим подписывающим лицом претензии и принимает на себя любой спор ( DISPUTE_CARRIER ).

последняя_пропагированная дисциплина

HeightSyncPeerTips.ShouldPropagateTo(recipient, h) возвращает true, если h > last_propagated[recipient] . При успешной отправке MarkPropagated(recipient, h) поднимает верхнюю отметку. Для достижения кворума на позднем хосте требуется лестница строго возрастающей высоты (верная в производстве), а не три ленивых переноса на одном и том же H .

14. API подтверждения

Контракт

// devshard/heightsync/confirmation.go type ConfirmState int const ( ConfirmPending ConfirmState = iota ConfirmConfirmed ConfirmStale ) type ConfirmationView интерфейс { IsStrictlyConfirmed ( h uint64 ) ConfirmState }

Семантика:

  • подтверждено — h очистил настроенное правило подтверждения. Нижестоящие протоколы МОГУТ рассматривать (h, hash) как авторитетные.
  • в ожидании — у функции синхронизации высоты есть данные для h, но правило еще не очищено. Нижестоящие компании НЕ ДОЛЖНЫ выносить состязательные вердикты; cPoC возвращает неопределенный результат ( C6 ).
  • stale — h не может быть вычислен, поскольку оракул устарел/отключен. Нижестоящая компания считает вердикты неубедительными до тех пор, пока они не будут восстановлены.

Монотонность: однажды подтвержденная высота остается подтвержденной. ожидание → подтверждено — единственный переход вперед.

Правила подтверждения

Настраивается во время развертывания:

Развертывания PoC без Strong run (C-quorum). При развертывании производственного класса СЛЕДУЕТ выбрать (C-гибрид) после включения Strong.

Память подтверждений и обрезка

  • При приеме: обновить запись для каждого отправителя, если высота находится в окне.
  • При продвижении кончика: компактный ( max_height < Tip − W_conf или Observe_at Past F ).
  • Защита монотонности: сохраните небольшой набор подтвержденных_высот, чтобы обрезка никогда не уменьшала подтвержденную высоту.

Индивидуальный просмотр, а не глобальный

IsStrictlyConfirmed вычисляется по собственному кольцу и часам аудита вызывающего абонента. Два верификатора могут временно не согласиться (ожидание или подтверждение); Срезка на основе кворума cPoC допускает это.

15. Интеграция cPoC — полный API

Следующие API Go представляют собой стабильную поверхность, которую используют cPoC и финализация. Пути реализации указаны в скобках.

15.1 Дискретный предикат подтверждения

// На стороне пользователя (курьера): func (c * Transport. HTTPClient) ConfirmationView() heightsync. ConfirmationView // На стороне хоста (собственный оракул): func (s * Transport. Сервер) ConfirmationView() heightsync. Просмотр подтверждения

Оба предоставляют ConfirmationView.IsStrictlyConfirmed(h uint64) ConfirmState . cPoC §C6 / §C14 / §Вердикт, шаг 5, позвоните напрямую.

Пример использования (вердикт cPoC):

просмотр := сервер . ConfirmationView() переключение вида. IsStrictlyConfirmed (h) {case heightsync. ConfirmConfirmed: // регистр вердикта фиксации heightsync. ConfirmPending: возвращает InconclusivePendingHeight(h) в случае heightsync. ConfirmStale: return InconclusiveStaleOracle() }

15.2 Наблюдаемая высота (пульс курьера)

func ( c * транспорт. HTTPClient) ObservedHeightNow() (uint64, bool)

Возвращает (h, true), где h — самая высокая свежая подсказка в кэше одноранговых подсказок курьера; (0, ложь), когда нет свежего наконечника или синхронизация высоты не настроена. Используется пульсом cPoC C14 — ложный возврат означает «Неубедительно — нет свежей высоты».

15.3 Доказательства, оправдывающие вину (уровень спора)

// На стороне пользователя: создать подписанный BLOB-объект отправителя для (originator, h). func ( c * транспорт. HTTPClient) HeightSyncEvidenceFor (строка источника, h int64,) (blob, sig [] байт, ok bool)

Возвращается кэшем курьера ( HeightSyncPeerTips.OriginSignedBlobFor ). Можно проверить с помощью heightsync.VerifyOriginDetached(verifier, sec, blob, sig) без доступа к пользователю.

15.4 Доказательства сильной степени (сильный режим)

// Хост-сторона: вернуть кэшированный LightBlock для h, если он доступен. func(s *transport. Сервер) LightBlockFor (h int64) (доказательство [] байт, ок bool)

Когда ведомый получателя прошел мимо спорного H , спорный пакет может содержать обе половины:

  • Подписанный BLOB-объект отправителя из HeightSyncEvidenceFor (виновник).
  • LightBlock получателя из LightBlockFor (каноническая пара).

Верификатор ложного спора возвращает DISPUTE_ORIGINATOR, когда оба пройдены.

15.5 Семена холодного запуска (опция)

// Опция сервера: транспорт. WithHeightSyncSeedRPC ( true ) // Вызов клиента: func ( c * Transport. HTTPClient) SeedHeightSync (ctx context. Контекст) (uint64, bool, ошибка)

Согласие POST /sessions/:id/height-sync : хост возвращает принудительную привязку (подписанную отправителем). Курьер проверяет + кэширует его перед выдачей первого вывода — полезно для кратковременных сеансов, когда первый вывод не выполняется в ходе синхронизации.

15.6 Принудительный синхронный поворот

// Оператор/спор/триггер cPoC: состояние. SendMsgForceHeightSyncTurn ( триггерНонсе , slotsNum , причина , StrongRequired )

Открывает ActiveForcedTurn в следующем диффе; каждый конверт в [trigger, Trigger + slots_num − 1] ДОЛЖЕН быть Anchor (или Strong, если StrongRequired = true ).

15.7 Аудит и споры с потребителями

тип AuditRing интерфейс { List ( строка PeerID ) [] AnchorAttestation ListPeers () [] строка ConfirmationView () ConfirmationView } func ( c * Transport. HTTPClient) HeightSyncAuditRing () * heightsync. Функция AuditRing (s*transport. Сервер) HeightSyncAuditRing () * heightsync. АудитРинг

Используется потребителями споров/завершений, которым необходимы дословные подтверждения и история каждого узла.

16. Модель атаки

Каждая строка отображает действия противника на защиту протокола и на тестовый сценарий, который это доказывает (полный каталог в height-sync-tests.md (https://github.com/a-kuprin/gonka/blob/devshard-testenv/devshard/docs/height-sync-tests.md)).

Внешние противники:

  • Злоумышленник, который контролирует > 2/3 валидаторов основной сети — вне защиты этого протокола; то же, что и любое консенсусное предположение L1.
  • Злоумышленник, который отравляет локальный оракул блока хоста — оракул блока имеет собственный закрепленный верификатор набора валидаторов (blockoracle/verifier); синхронизация высоты не проверяется повторно.

17. Значения по умолчанию и конфигурация

Порядок чтения для участников

  • §6 — схема архитектуры. Создайте мысленную модель хост-производителя (собственный оракул, ветвь ответа на знаки) и пользователя-курьера (одноранговый кеш, носитель запроса), питающих один конвейер-получатель.
  • §8 — три режима синхронизации и диаграмма состояний.
  • §11 — блок-схема конвейера приемника; это нормативный раздел по несущей способности.
  • §12 — асимметричная модель подписи.
  • §14 + §15 — что на самом деле потребляет cPoC.
a-kuprin avatar
a-kuprinMaintainerMaintainer

Важным моментом в соответствии с этим предложением является то, что часть консенсуса выходит за рамки и должна быть здесь: Протокол финализации (https://github.com/gonka-ai/gonka/discussions/1369).

Это оптимистичный протокол — мы верим, пока высота находится в некоторой отметке от нашей известной высоты. Но мы всегда подписываемся хэшем блока отправителя, поэтому позже, когда мы получим ту же высоту от dapi, если мы увидим, что хеш блока отличается, мы можем начать спор, и создатель недействительных данных будет наказан.

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

shd avatar

Случайные комментарии, часть 1.

  • Я бы предложил подробно описать «неоптимистический путь». Насколько я понимаю, этот путь ведет к раунду консенсуса, но, возможно, это следует описать лучше. В частности, «Сильный (LightBlock + VerifyCommit)» — либо необходимы ссылки на протокол, либо должно быть предоставлено его описание. Существующее описание слишком схематично (по крайней мере, на мой посторонний взгляд).

Также я бы предложил более подробно описать использование height/block_hash: возможно, после этого станут очевидными некоторые улучшения алгоритма.

  • Несколько случайных вопросов и идей по поводу D (максимального интервала между соседними высотами):

... Мы доверяем высотам в будущем без дополнительных доказательств в сети, если высота близка к той, которая, как мы знаем, является текущей. Если между хостами существуют большие разногласия по высоте (height_in_the_future -known_height = |Δ| > D), мы используем полные данные из основной сети (хэш блока и подписи валидаторов) для проверки высоты...

Что, если разница во времени между одноразовыми номерами достаточно велика? Например, 1 минута? Это приведет к большим разногласиям и раунду консенсуса (даже несмотря на то, что нет реальной причины для спора: модель атаки 13, длительное время между блоками (подача тихая, кешированный совет все еще действителен)). Представьте, что запросы на вывод приходят с интервалом в 1 минуту: каждый будет сопровождаться консенсусом.

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

Здесь у нас могут быть следующие предложения:

а) (Может быть, это неправильно), но основное использование высоты — это обнаружение фазы PoC: если пользователь запрашивает вывод, но узел отвечает «отклонено из-за PoC». В этом случае пользователь немедленно отправляет запрос следующему узлу, и ответ приходит немедленно, и D важен. Таким образом, проверка на D может быть активирована только в некоторых случаях. В противном случае мы всегда доверяем прогрессу высоты, если только предыдущая высота не была раньше нашей.

б) Пользователь может указать текущее время в запросе - поэтому хост должен предоставить высоту блока, ближайшую к времени пользователя, возможно, +-1 слот. В случае, если время пользователя слишком отличается от времени хоста, можно активировать консенсус (сильная фаза): хосты отправляют всем текущее время пользователя и его собственное текущее время. В случае 5-секундного консенсуса (сопоставимого со скоростью консенсуса в основной сети) этот механизм может быть достаточно эффективным.

в) Адаптивные актуальные гарантии высоты. Пользователь делает пустые запросы каждые 5 секунд, он должен их выполнить (чтобы эти запросы получали последнюю высоту в нужные промежутки времени, а изменения высоты всегда были либо 0, либо 1) -- или выполняет консенсус в случае длительной паузы.

Консенсус может быть очень долгим по сравнению с этими пустыми сообщениями. Эти сообщения могут передаваться в отдельном циклическом переборе, то есть не влиять на следующий узел вывода. Они могут остановиться после (N^2/2) пустых запросов, то есть остановиться прямо перед тем, как цена поддержки работоспособности станет больше, чем цена консенсуса после восстановления.

  • Источник случайности: а) предлагаемый метод оставляет некоторое пространство для манипуляций (поскольку разница высот < D дает возможность выбора наилучшего хеша). б) в случае хоста противника И пользователя эта возможность становится гарантией (пользователь и хост вместе всегда могут выбрать подходящую задержку для получения необходимого хэша - например, для выбора правильного следующего узла) в) поэтому здесь могут быть предпочтительны схемы фиксации: если хост и пользователь из «разных команд» d) в случае, если хост И пользователь вместе являются противниками, могут быть предложены дополнительные подходы, точная формулировка выходит за рамки комментария.
akup avatar
akupMaintainerMaintainer
2026-07-01

Я думаю, мы начнем еще одну дискуссию в зависимости от источника случайности.

хэш блока может быть источником случайности и вполне естественен, когда у нас есть протокол синхронизации высоты (который нам в любом случае нужен для обработки cPoC на devshard). Но источник случайности в стиле Педерсона, на мой взгляд, очень элегантен. Пожалуйста, запишите это предложение

shd avatar

Важная информация о D: элементарный анализ показывает, что на границе периода неактивности 30 секунд/1 минуты требуется значительное увеличение количества раундов консенсуса.

Очень приблизительные фактические данные: в эпоху 263 было 590 000 запросов на вывод на 44 пользователя, что дает T = 0,15 запросов в секунду. Мы можем считать (в качестве первого предположения), что запросы на вывод следуют распределению Пуассона, это дает e^(-0,15 * 60) = 0,0001 вероятность 60-секундного бездействия; и e^(-0,15 * 30) = 0,01 вероятность 30-секундного бездействия.

Конечно, распределение другое (запросы обычно соответствуют какому-то процессу вывода, то есть они не являются независимыми), и точные цифры будут другими. Но D может повлиять на производительность весьма необычным образом, если умеренная пауза в выводах приведет к новому раунду консенсуса.

akup avatar
akupMaintainerMaintainer
2026-07-01

D здесь не связан с одноразовыми номерами/блоками, он связан с высотой блока основной сети.

Если мы находимся на разнице высот > D, нам следует запустить раунд консенсуса, и время приращения блока в основной сети довольно стабильно, около 5 секунд, но при высокой нагрузке зимой мы наблюдали медленное построение блоков, когда один блок увеличивался примерно за 30-45 секунд.

shd avatar
shdMaintainerMaintainer
2026-06-22

Вопрос: Существует вектор атаки, модель атаки 13, Длительное время между блоками (отключение канала, кэшированная подсказка все еще действительна). Однако есть ли какая-либо конкретная атака/случай длительного бездействия пользователя?

akup avatar
akupMaintainerMaintainer

Это было описано отдельно в предложении протокола cPoC. На него есть ссылка в этом документе в репозиториях GitHub, но я только что опубликовал его как обсуждение: # 1384 (https://github.com/gonka-ai/gonka/discussions/1384).

Есть случаи для обработки параграфа и особенно C14 — стратегическая задержка при низкой нагрузке (подавление пульса разработчика), обсуждающая именно это долгое бездействие пользователя.

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

shd avatar

Генерация случайных чисел

Генерация случайных чисел из хеша блокчейна самого последнего блока основной сети имеет много преимуществ, однако имеет следующие проблемы:

  • он не позволяет генерировать номер "на месте" --- требует задержки до прибытия нового блока основной сети.

он не позволяет генерировать номер "на месте" --- требует задержки до прибытия нового блока основной сети.

  • задержки синхронизации: генерирующая сторона имеет некоторую возможность выбирать из нескольких заголовков (в диапазоне D последовательных заголовков) и, следовательно, имеет некоторое влияние на количество.

задержки синхронизации: генерирующая сторона имеет некоторую возможность выбирать из нескольких заголовков (в диапазоне D последовательных заголовков) и, следовательно, имеет некоторое влияние на количество.

Обе проблемы можно решить, но за это приходится платить, поэтому рекомендуется рассмотреть разные подходы.

Альтернативный подход

В качестве простого альтернативного подхода мы предлагаем использовать стандартную схему обязательств. Возьмем какую-нибудь надежную хеш-функцию (например, sha256) --- назовем ее F(x), где x — двоичная строка.

Тогда генерация случайных чисел (для пользователя U и хоста H) может следовать следующей схеме:

  • Каждая сторона (U и H) генерирует одно случайное число --- пусть это будут R_U и R_H. Это должно быть длинное число, например. 128 или 256 бит, чтобы предотвратить угадывание числа.

Каждая сторона (U и H) генерирует одно случайное число --- пусть это будут R_U и R_H. Это должно быть длинное число, например. 128 или 256 бит, чтобы предотвратить угадывание числа.

  • Затем стороны обмениваются хэшами числа --- H отправляет F(R_H) в U, а U отправляет F(R_U) в H.

Затем стороны обмениваются хэшами числа --- H отправляет F(R_H) в U, а U отправляет F(R_U) в H.

  • После обмена значениями стороны обмениваются исходными случайными числами: таким образом, каждая из сторон знает R_U, R_H, F(R_U) и F(R_H).

После обмена значениями стороны обмениваются исходными случайными числами: таким образом, каждая из сторон знает R_U, R_H, F(R_U) и F(R_H).

  • Значение Xor(R_U,R_H) является желаемым случайным значением.

Значение Xor(R_U,R_H) является желаемым случайным значением.

Каковы свойства этого протокола, анализ его безопасности

  • Если числа R_U и R_H сгенерированы без знания друг друга, и хотя бы одна из сторон сгенерировала их случайным образом, то результирующее значение также является случайным.

Если числа R_U и R_H сгенерированы без знания друг друга, и хотя бы одна из сторон сгенерировала их случайным образом, то результирующее значение также является случайным.

  • Значения F(R_U) и F(R_H) не раскрывают никакой информации о R_U, R_H, хотя в случае коротких чисел возможен перебор (поэтому у нас есть требование 128 или 256 бит).

Значения F(R_U) и F(R_H) не раскрывают никакой информации о R_U, R_H, хотя в случае коротких чисел возможен перебор (поэтому у нас есть требование 128 или 256 бит).

  • Стороны не могут изменить свое решение после обмена хэшами.

Стороны не могут изменить свое решение после обмена хэшами.

Хэш-коллизии

Sha256 по-прежнему считается устойчивым к коллизиям, однако для полноты текста необходимо отметить, что эту схему можно сделать более устойчивой к таким атакам, даже если такие коллизии будут обнаружены (или используется более слабая хеш-функция).

Например, у вас есть пара чисел, которые имеют один и тот же хэш, но разные значения: A,B такие, что F(A) = F(B). Таким образом, в момент раскрытия чисел у противника может быть выбор (либо А, либо Б), что может привести к некоторому контролю над полученным случайным числом.

Чтобы предотвратить это, можно добавить к схеме обмен одноразовыми номерами:

  • Стороны обмениваются двумя случайными числами (N_U и N_U соответственно), и случайные числа, генерируемые на шаге 1, должны включать эти числа в качестве префиксов: R'_U = N_H++R_U и R'_H = N_U++R_H, где ++ — объединение двоичных строк.

Эта модификация должна запретить другой стороне использовать ранее вычисленные коллизии в хэш-функциях. Необходимость такого усложнения генерации случайности дискуссионна.

Общий анализ

  • Эта схема безопасна, устойчива к враждебному поведению сторон и генерирует хороший рандом, если в этом заинтересована хотя бы одна из сторон.

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

  • Эту схему можно активировать по требованию в любой момент, и она довольно дешевая. Правда, для этого требуется 2 элементарных сообщения (или 3, в более безопасном варианте) от каждой стороны.

Эту схему можно активировать по требованию в любой момент, и она довольно дешевая. Правда, для этого требуется 2 элементарных сообщения (или 3, в более безопасном варианте) от каждой стороны.

  • Однако он не сопротивляется совместной случайной генерации: когда и U, и H вместе пытаются подделать желаемое число. Хэш-схема блокчейна также в некоторой степени имеет эту проблему.

Однако он не сопротивляется совместной случайной генерации: когда и U, и H вместе пытаются подделать желаемое число. Хэш-схема блокчейна также в некоторой степени имеет эту проблему.

Проверка вывода, устойчивая к сотрудничеству (проект предложения)

Необходимость многохостовых девшардов продиктована необходимостью распределить контроль над процессом инференса, сделать его прозрачным и честным. Однако совместное поведение пользователя и некоторых хостов может повредить гарантиям. В частности, такое сотрудничество может легко поручить проверку скомпрометированных выводов враждебным хостам, тем самым повредив всю схему.

Чтобы этого не произошло, предлагается следующая идея.

  • Каждый хост должен хранить последние 32 (по размеру devshard) вывода, готовые для будущей проверки. Фактический проверяющий и фактический вывод, который будет проверен, на данный момент никому не известны.

Каждый хост должен хранить последние 32 (по размеру devshard) вывода, готовые для будущей проверки. Фактический проверяющий и фактический вывод, который будет проверен, на данный момент никому не известны.

  • Хост и верификатор для данного вывода определяются путем генерации случайных чисел.

Хост и верификатор для данного вывода определяются путем генерации случайных чисел.

  • Затем верификатор запрашивает у хоста вывода его прошлую задачу (одну из 32) и выполняет необходимую проверку. Если случайное число не всегда генерируется противниками, а хотя бы иногда (фактически в 2/3 случаев) действительно случайно, это дает хорошую гарантию от мошенничества.

Затем верификатор запрашивает у хоста вывода его прошлую задачу (одну из 32) и выполняет необходимую проверку. Если случайное число не всегда генерируется противниками, а хотя бы иногда (фактически в 2/3 случаев) действительно случайно, это дает хорошую гарантию от мошенничества.

Эта идея не решает всех проблем с честностью пользователя и хостов, но дает дополнительный уровень защиты.

Главный вопрос здесь — как генерировать случайные числа. Текст ниже объясняет это.

Генерация случайных чисел

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

Представьте себе, пользователь U с хостом H генерирует число, и это число определяет верификатора (и они злоупотребляют протоколом, чтобы выбрать верификатора из своей команды). Это приводит нас к выводу, что число должно генерироваться всеми остальными хостами в devshard. Например, это может быть последовательная генерация: для первого вывода H_0 число генерируется U и H_1. Для второго вывода H_0 — по U и H_2 и так далее. Таким образом, после 32 раундов (или 32 выводов каждого хоста) по крайней мере 20 из них будут случайно выбраны несотрудничающей стороной.

Итак, у нас должно быть два циклических цикла: цикл вывода (всем узлам предлагается сделать выводы последовательно, чтобы минимизировать пространство для затенения узлов) и цикл проверки.

Конкретные детали еще предстоит определить, но основная идея в том, что этот цикл должен сознательно идти с разной скоростью (по сравнению с циклом вывода). Чтобы в течение одного цикла вывода (чтобы вывод, начиная с H_0, возвращался обратно в H_0), хосты цикла проверки должны быть сдвинуты (verificatoin.H_0 с inference.H0 -- они начинаются на одном и том же хосте, но в момент возврата обратно в H_0 проверка должна быть в H_k с k/= 0).

Так как в сети 2/3 честных хостов, то за 2 из 3 циклов хост окажется в паре с хостом, которого нет в его команде противника. И после 11 циклов у каждого хоста будет хотя бы одна проверка, контролируемая независимым хостом.

Цикл вывода может быть облегченным (то есть он может не иметь независимого блокчейна), и все транзакции могут быть добавлены в одну и ту же последовательность различий; эти два цикла могут быть переплетены в одном блокчейне devshard детерменистическим образом.

Кроме того, этот цикл проверки можно использовать для распространения высоты блока (чтобы избежать частого вызова консенсуса).

a-kuprin avatar
a-kuprinMaintainerMaintainer
2026-07-02

Я думаю, что этот стиль обязательства Педерсона для источника случайности является лучшим вариантом для финализации (https://github.com/gonka-ai/gonka/discussions/1369) выбора сборщика для обязательства.

В любом случае нам следует вернуться к протоколу финализации, поскольку BLS очень хорошо подходит для него, как мы уже обсуждали.

Оригинал
alexanderkuprin avatar
alexanderkuprinАвтор

Height-sync protocol

User ↔ host envelopes carry an optional HeightSyncSection that attests to a mainnet (height, block_hash, block_timestamp) triple. This section is the sole input to cross-host time alignment, timeout decisions, and the IsStrictlyConfirmed(h) predicate that downstream protocols (cPoC, finalization) gate verdicts on. block_hash could be the source of determenistic randomness that is unknown in advance, can be used by VALIDATION_PROTOCOL_PROPOSAL.md (https://github.com/a-kuprin/gonka/blob/devshard-testenv/devshard/docs/proposals/VALIDATION_PROTOCOL_PROPOSAL.md) There is another variants for source of randomness to be discussed.

block_timestamp can be deterministic time to be used in devshard for timeouts.

This document is the canonical, single-version spec. The in-tree implementation matches this document; the test catalog ( height-sync-tests.md (https://github.com/a-kuprin/gonka/blob/devshard-testenv/devshard/docs/height-sync-tests.md) ) lists what is already proven and what is planned.

Related docs: height-sync-tests.md (https://github.com/a-kuprin/gonka/blob/devshard-testenv/devshard/docs/height-sync-tests.md) (test catalog — implemented and planned), CPOC Protocol for devshard (https://github.com/gonka-ai/gonka/discussions/1384) , Finalization Protocol (https://github.com/gonka-ai/gonka/discussions/1369) , VALIDATION_PROTOCOL_PROPOSAL.md (https://github.com/a-kuprin/gonka/blob/devshard-testenv/devshard/docs/proposals/VALIDATION_PROTOCOL_PROPOSAL.md) .

Table of contents

Summary (#1-summary)

Problem (#2-problem)

High-level overview of protocol (#3-high-level-overview-of-protocol)

Goals (#4-goals)

Glossary (#5-glossary)

Architecture overview (#6-architecture-overview)

Wire format (#7-wire-format)

Sync modes (Omit / Anchor / Strong) (#8-sync-modes-omit--anchor--strong)

Cadence (sync turns, K , slots_num , forced turns) (#9-cadence)

Producer rules (#10-producer-rules)

Receiver pipeline (#11-receiver-pipeline)

Trust model and signatures (#12-trust-model-and-signatures)

Carry-forward, provenance, attribution (#13-carry-forward-provenance-attribution)

Confirmation API ( IsStrictlyConfirmed ) (#14-confirmation-api)

cPoC integration — full API (#15-cpoc-integration-api)

Attack model and mitigations (#16-attack-model)

Defaults and configuration (#17-defaults-and-configuration)

Status and milestones (#18-status-and-milestones)

1. Summary

User–host inference traffic carries a two-section HTTP body :

  • HeightSyncSection — optional mainnet attestation: a signed (mainnet_height, mainnet_block_hash) pair, plus framing and provenance metadata.
  • message_body — the application payload (opaque to height sync).

Section 1 is emitted only when needed :

  • Sync turn — the standard cadence: every K nonces, a window of slots_num consecutive nonces carries Anchor; in between, Omit.
  • Forced sync turn — MsgForceHeightSyncTurn opens a slots_num -wide Anchor span at any nonce (cPoC dispute open, operator force).
  • |Δ| > D — when the sender's claimed height differs from the receiver's aligned height by more than D , the sender MUST use Strong ( LightBlock + VerifyCommit ); otherwise the receiver rejects.

Hosts sign response-leg Anchors with their secp256k1 signer key; courier users carry these signed blobs forward, verifying on ingest and using them as on-demand exculpation proof. Request-leg Anchors are trusted by hosts (no inline signature) — the user proves provenance later if disputed.

A single IsStrictlyConfirmed(h) predicate exposes a discrete {confirmed, pending, stale} answer to downstream consumers.

2. Problem

At devshard we need source of mainnet height and randomness, that is unknowm in advance but is determenistic to make all hosts and user to aggree.

Each host has it's own latest (height, block_hash) oracle (inference chain grpc), and the prove by inference chain validators signatures. But hosts should aggree that any height provided to protocol is really latest. So there should be height sync protocol provided in this doc.

3. High-level overview of protocol

As devshard is designed for high throughput we aim to minimize extra data transferred in messages and minimize checks of minnet signatures. Also we are minimizing gossipping that should happen only on disputes and settlement to minimize traffic (as one host could be in a lot of devshard's)

So main design decitions are made:

  • Height synchronization happens not on every nonce but only in specialized windows, where we add to request/response only (height, block_hash) and originator's (host that is responding to request) signature, to proove origin of this data for possible disputes. User is carrying forward (height, block_hash) at next requests to propogate this data to other hosts.
  • We trust heights in the future without additional mainnet proove if the height is close to the one we know is current. If there is a large disagreement between hosts on height ( height_in_the_future - known_height = |Δ| > D ) we use the full data from mainnet (block hash and validators signatures) to validate the height

If we find that any earlier provided by any host height doesn't match the oracle block_hash we start the dispute

As the result we provide API at devshardd and devshardctl that gives latest height, block hash and the knowledge if majority of devshard network participants agree on this.

4. Goals

  • Cheap periodic alignment — Anchor (no LightBlock ) on a sync-turn schedule keeps every host's view of mainnet time within a bounded window without per-message proof overhead.
  • Strong escalation on disagreement — once |Δ| > D or finalization requires it, validator-quorum-bound proof ( LightBlock ) is mandatory.
  • Provenance and attribution — every cached (H, hash) is traceable to the originating host signature; carriers cannot be blamed for forwarding a malicious host's signed claim, and carriers that strip provenance become the cryptographic source.
  • Replay resistance — freshness budget F on originator timestamp + per-recipient last-propagated bookkeeping prevents a carrier from re-using stale or already-delivered tips.
  • Confirmation contract for downstream consumers — discrete IsStrictlyConfirmed(h) ∈ {confirmed, pending, stale} predicate so cPoC / finalization do not invent their own quorum logic.
  • Courier-only deployment — users with no mainnet follower of their own can still carry signed host tips between hosts in the round-robin and reach (C-quorum) confirmation.

5. Glossary

6. Architecture overview

flowchart LR subgraph mainnet [Gonka mainnet] BC[CometBFT consensus] end BC -- "block headers + commits" --> HSD[heightsyncd / blockoracle] subgraph host [Host runtime] HSD --> SCH_H[AnchorScheduler<br/>local-oracle source] SCH_H --> TX_H[transport.Server<br/>signs response leg] TX_H -- "HeightSyncSection<br/>request inbound" --> RX_H[Receiver pipeline<br/>D-band, freshness, classify] RX_H --> AUD_H[AuditRing + ConfirmationIndex] AUD_H -- "IsStrictlyConfirmed" --> CPOC_H[cPoC consumer] end subgraph user [Courier user / devshardctl] TIPS[HeightSyncPeerTips<br/>verbatim signed blobs] --> SCH_U[AnchorScheduler<br/>peer-tip source] SCH_U --> TX_U[transport.HTTPClient<br/>Carry, strip sig on request] TX_U -- "response inbound<br/>verify + cache" --> RX_U[Verify response Anchor<br/>RecordOriginWithBlob] RX_U --> TIPS TIPS -- "IsStrictlyConfirmed" --> CPOC_U[cPoC consumer] end TX_U -- "request leg<br/>HeightSyncSection" --> RX_H TX_H -- "response leg<br/>HeightSyncSection (signed)" --> RX_U

Key invariants:

  • Each host has its own mainnet follower ( heightsyncd /blockoracle); this is the canonical source of local_aligned .
  • The user has no follower (courier mode); it derives local_aligned from the verified peer-tip cache populated by signed host responses.
  • HeightSyncSection is the only mainnet-related wire surface on inference envelopes; the receiver pipeline is single-entry.

7. Wire format

HeightSyncSection is carried as protobuf field on the inference envelope and JSON-mirrored for tooling. Field numbers are stable.

Notes:

  • Degraded Anchor (quiet feed). When the local oracle has not received a new block within StaleAfter but Latest() still returns a cached header, hosts emit a normal Anchor (fields 1–8) plus field 10. This avoids sync-turn response Omit during long inter-block gaps; consensus across hosts still corrects a minority with an outdated tip. Omit remains mandatory when there is no cached tip (feed never started), Latest() fails (feed unavailable), or the courier peer-tip cache is empty.
  • Direction-bound signatures. Field 8 is set by hosts on responses only. Carry() clears field 8 before sending on the request leg; inbound request validation does not require an inline signature.
  • Canonical signing input. CanonicalOriginBytes(sec) = "heightsync.origin.v1" || proto.Marshal(fields 1..7) . Field 8 is not part of the signing input.
  • Wire-level reservation. origin_attestation (embedded originator blob) is reserved for future inline-embed deployments; current protocol uses the asymmetric model (response signed, request trusted, on-demand exculpation).

JSON mirror:

{ "height_sync" : { "proof_type" : " height-anchor-v1 " , "mainnet_height" : 42 , "mainnet_block_hash_hex" : " abc... " , "timestamp_unix_ms" : 1700000000000 , "direction" : " response " , "originator_sender_id" : " gonka1host... " , "originator_timestamp_unix_ms" : 1700000000000 , "sender_signature" : " base64... " , "light_block" : " base64... " , "tip_stale_after_ms" : 12000 } }

( tip_stale_after_ms omitted when the cached tip is fresh.)

8. Sync modes (Omit / Anchor / Strong)

stateDiagram-v2 direction LR [*] --> Omit Omit --> Anchor: nonce in sync turn / forced turn / lazy carry Anchor --> Omit: next nonce outside window Anchor --> Strong: \|H − local_aligned\| > D OR forced (StrongRequired) Strong --> Anchor: peer realigned, within D again Anchor --> Anchor: cadence next turn Strong --> Strong: still > D

Periodic alignment uses Anchor only . Strong is not a default cadence step — it is the disagreement / dispute path.

Quiet feed vs dead feed (hosts):

9. Cadence

Sync-turn windows

For a session direction, on outgoing nonce n :

  • Initial sync turn: 1 ≤ n ≤ slots_num → Anchor (or Strong, see §10).
  • Periodic sync turns: for every i ≥ 1 , i·K ≤ n ≤ i·K + slots_num − 1 → Anchor.
  • All other nonces → Omit , unless a force directive or lazy carry-forward applies.

Constraint: K ≥ slots_num so windows never overlap.

gantt title Sync-turn cadence (K=8, slots_num=4) dateFormat X axisFormat %s section Cadence Initial sync turn (Anchor) :a1, 1, 4 Omit :a2, 5, 7 Periodic sync turn 1 :a3, 8, 11 Omit :a4, 12, 15 Periodic sync turn 2 :a5, 16, 19

Forced sync turn

MsgForceHeightSyncTurn(trigger_nonce, slots_num, reason, strong_required?) opens an ActiveForcedTurn{start, end} span:

  • Both directions MUST emit Anchor for every envelope in [start, end] . Omit inside a forced turn is INVALID.
  • strong_required = true upgrades the window to Strong.
  • A second directive while a turn is active is silently ignored .
  • A forced window that overlaps the next cadence window swallows it (no double-Anchor on boundary).
  • After n > end , cadence resumes from the standard rule.

Lazy carry-forward (courier deployments)

Outside any sync-turn window, the courier user MAY emit Anchor on a request leg iff:

  • The peer-tip cache holds a fresh originator section ( MaxFresh(now, F) returns non-nil).
  • cached_max_height > last_propagated[recipient] .

The receiver classifies this as VALID_LAZY_ANCHOR (audit tag lazy ); it does not open a sync-turn obligation.

10. Producer rules

Hosts (have own oracle)

  • On every outbound response : consult the local oracle; if a sync turn or forced turn applies, emit Anchor with OriginatorSenderID = host_address , OriginatorTimestampMs = now , and sign the section (field 8). If the oracle is quiet (no new block within StaleAfter ) but a cached tip exists, still emit that Anchor and set tip_stale_after_ms to the age of the last ingested block (field 10 is set after signing input fields 1–7). Omit only when there is no usable cached tip or Latest() fails.
  • If forced.StrongRequired is set OR receiver's peer_aligned_height differs from local tip by > D : produce Strong by attaching the cached LightBlock for H (field 9).
  • On inbound requests : do not sign anything; classify via the receiver pipeline (§11).

Courier user (no own oracle)

  • Maintain HeightSyncPeerTips keyed by OriginatorSenderID .
  • Verify host responses on ingest ( VerifyOrigin ); on failure, drop the tip and increment origin_sig_invalid_total .
  • On outbound requests : consult the scheduler; lazy carry only when the cache has a tip not yet propagated to the recipient. Clear field 8 ( sender_signature ) before sending.
  • Producer never sets OriginatorSenderID = user_address ; that field reflects the host that signed the cached blob.

11. Receiver pipeline

flowchart TD A[envelope arrives] --> B{HeightSyncSection<br/>present?} B -- no --> O{nonce in sync turn /<br/>active forced turn?} O -- yes --> O1[INVALID<br/>sync_turn_anchor_missing] O -- no --> O2[VALID_OMIT] B -- yes --> C{Anchor or Strong?} C -- Anchor --> D{"|H − local_aligned| > D?"} D -- yes --> D1[INVALID<br/>strong_required] D -- no --> E{carry-forward<br/>originator set?} E -- yes --> F{originator within F?} F -- no --> F1[INVALID<br/>stale_origin] F -- yes --> G[classify cadence / lazy<br/>by nonce vs sync turn] E -- no --> G C -- Strong --> H[StrongVerifier.VerifyLightBlock] H -- ok --> I[VALID_STRONG] H -- fail --> H1[INVALID<br/>strong_proof_invalid] G --> J{block H local AND<br/>hash matches?} J -- yes/match --> K[VALID_ANCHOR or<br/>VALID_LAZY_ANCHOR] J -- no/local-missing --> L[enqueue deferred check] J -- local AND mismatch --> M{originator present?} M -- yes --> M1[DISPUTE_ORIGINATOR] M -- no --> M2[DISPUTE_CARRIER]

Normative steps for a non-Omit envelope:

  • Parse + framing (proto / JSON).
  • Forced-turn check first. If ActiveForcedTurn[start..end] is set and start ≤ nonce ≤ end , the envelope MUST be Anchor (or Strong when StrongRequired ). Omit ⇒ INVALID.
  • D band. If proof_type == "height-anchor-v1" and |H − local_aligned| > D : INVALID ( strong_required ).
  • Strong path. If proof_type == "cometbft-light-block-v1" : run StrongVerifier.VerifyLightBlock (chain id, header vs claims, validators_hash , optional epoch-bound Step 3b, BlockID , commit > 2/3 ); failure ⇒ INVALID ( strong_proof_invalid ).
  • Originator presence and freshness. If OriginatorSenderID != "" : If now_ms − OriginatorTimestampMs > F ⇒ INVALID ( stale_origin ); audit trust = TrustDisputeCarrier . Else continue.
  • If now_ms − OriginatorTimestampMs > F ⇒ INVALID ( stale_origin ); audit trust = TrustDisputeCarrier .
  • Else continue.
  • Cadence / lazy classification. Inside sync-turn (cadence / initial / forced): VALID_ANCHOR (tag cadence ). Outside sync-turn + originator present (courier): VALID_LAZY_ANCHOR (tag lazy ). Outside sync-turn + originator absent + Anchor: legacy host self-attestation; VALID_ANCHOR .
  • Inside sync-turn (cadence / initial / forced): VALID_ANCHOR (tag cadence ).
  • Outside sync-turn + originator present (courier): VALID_LAZY_ANCHOR (tag lazy ).
  • Outside sync-turn + originator absent + Anchor: legacy host self-attestation; VALID_ANCHOR .
  • Local oracle reconciliation. If block H is local and hash matches → confirmed immediately; feed ConfirmationIndex . If H is not yet local → enqueue deferred check by (originator, H, hash) ; do not advance height_seen_max . If H is local and hash differs → DISPUTE_ORIGINATOR (originator metadata present) or DISPUTE_CARRIER (originator absent or signature failed); persist the offending signed blob.
  • If block H is local and hash matches → confirmed immediately; feed ConfirmationIndex .
  • If H is not yet local → enqueue deferred check by (originator, H, hash) ; do not advance height_seen_max .
  • If H is local and hash differs → DISPUTE_ORIGINATOR (originator metadata present) or DISPUTE_CARRIER (originator absent or signature failed); persist the offending signed blob.
  • Audit + metrics. Append AnchorAttestation (with Tag , Trust , OriginatorSenderID , OriginSignedBlobAvailable ) to the per-peer ring; emit counters.
  • Process message_body if not INVALID.

Result classes

12. Trust model and signatures

The protocol is asymmetric : responses are signed, requests are trusted, exculpation is on-demand.

sequenceDiagram participant U as User (courier) participant H as Host A participant H2 as Host B U->>H: request (request leg, no sig) H->>U: response Anchor [signed by Host A] Note over U: VerifyOrigin OK<br/>RecordOriginWithBlob(host_A, H, blob, sig) U->>H2: request Anchor (carry-forward, no inline sig) Note over H2: trusts carrier<br/>freshness gate F applies Note over H2: later, follower advances<br/>compares hash to canonical H2-->>U: DEFERRED_FAIL? open dispute vs user U->>U: HeightSyncEvidenceFor(host_A, H) → blob + sig U-->>H2: signed_blob proves originator = Host A<br/>⇒ DISPUTE_ORIGINATOR vs Host A

Response leg (host → user)

  • Host fills OriginatorSenderID , OriginatorTimestampMs , builds CanonicalOriginBytes (fields 1–7 + domain heightsync.origin.v1 ).
  • Signs with the host's secp256k1 key, sets field 8.
  • User verifies field 8 on ingest. Fail ⇒ drop, no cache, no propagation; origin_sig_invalid_total increments.
  • On success: RecordOriginWithBlob(originator, sec, blob, sig) .

Request leg (user → host)

  • Carry copies originator fields (6, 7) from the cached blob.
  • Carry() strips field 8 before sending.
  • Host accepts the section subject to receiver pipeline (§11). No inline signature is required or verified.

Exculpation

If a host later opens a dispute against the user-carrier, the user calls HTTPClient.HeightSyncEvidenceFor(originator, H) to produce the cached (blob, sig) . A dispute verifier re-runs VerifyOrigin ; success ⇒ blame shifts to the originating host ( DISPUTE_ORIGINATOR ); failure ⇒ blame stays on the carrier ( DISPUTE_CARRIER ).

Strong proof

When Strong is on the wire ( light_block non-empty), validation is cryptographic against the pinned validator set:

  • Decode bytes as LightBlock -equivalent ( blockoracle.Header ).
  • Check chain_id , height , block_hash against claims.
  • Verify validators_hash matches Merkle root of the pinned set.
  • (Optional) Step 3b — verify against per-epoch participant set.
  • Verify BlockID == hdr.BlockID .
  • Run VerifyCommit : every commit signature ecrecovers to a pinned validator, no duplicates, accumulated power strictly > 2/3 of total.
  • (Optional) Recency: h ≥ local_tip − max_lag_blocks else VALID_STALE .

13. Carry-forward, provenance, attribution

Rules

  • Originator fields are immutable across hops. Carrier MUST NOT overwrite OriginatorSenderID or OriginatorTimestampMs .
  • D bound on carry-forward. A carry-forward Anchor with |H − local_aligned| > D is INVALID; carrier MUST escalate to Strong instead. (This is a stricter form of strong_required .)
  • Sender signature stripped on request. The user's outbound request never carries field 8. The host trusts the request based on freshness + cadence rules; cryptographic proof lives in the user's cached blob.
  • Provenance-less carry = carrier is the source. If a user forwards a section with empty originator fields, the carrier becomes the cryptographic signer of the claim and absorbs any dispute ( DISPUTE_CARRIER ).

last_propagated discipline

HeightSyncPeerTips.ShouldPropagateTo(recipient, h) returns true iff h > last_propagated[recipient] . On a successful send, MarkPropagated(recipient, h) advances the high-water mark. Reaching a quorum at a late host requires a strictly increasing height ladder (production-faithful) — not three lazy carries at the same H .

14. Confirmation API

Contract

// devshard/heightsync/confirmation.go type ConfirmState int const ( ConfirmPending ConfirmState = iota ConfirmConfirmed ConfirmStale ) type ConfirmationView interface { IsStrictlyConfirmed ( h uint64 ) ConfirmState }

Semantics:

  • confirmed — h has cleared the configured confirmation rule. Downstream protocols MAY treat (h, hash) as authoritative.
  • pending — height-sync has data for h but has not yet cleared the rule. Downstream MUST NOT commit adversarial verdicts; cPoC returns Inconclusive ( C6 ).
  • stale — h cannot be evaluated because the oracle is stale / disconnected. Downstream treats verdicts as Inconclusive until recovery.

Monotonicity: once confirmed , a height stays confirmed . pending → confirmed is the only forward transition.

Confirmation rules

Configured at deployment time:

PoC deployments without Strong run (C-quorum) . Production-class deployments SHOULD select (C-hybrid) once Strong is enabled.

Confirmation memory and pruning

  • On ingest: upsert per-originator entry if height is in window.
  • On tip advance: compact ( max_height < tip − W_conf or observed_at past F ).
  • Monotonicity guard: retain a small confirmed_heights set so pruning never demotes a confirmed height.

Per- V view, not global

IsStrictlyConfirmed is computed against the caller's own audit ring and clock. Two verifiers may transiently disagree ( pending vs confirmed ); cPoC's quorum-based slashing tolerates this.

15. cPoC integration — full API

The following Go APIs are the stable surface that cPoC and finalization consume. Implementation paths in parentheses.

15.1 Discrete confirmation predicate

// On the user side (courier): func ( c * transport. HTTPClient ) ConfirmationView () heightsync. ConfirmationView // On the host side (own oracle): func ( s * transport. Server ) ConfirmationView () heightsync. ConfirmationView

Both expose ConfirmationView.IsStrictlyConfirmed(h uint64) ConfirmState . cPoC §C6 / §C14 / §Verdict step 5 call this directly.

Usage example (cPoC verdict):

view := server . ConfirmationView () switch view . IsStrictlyConfirmed ( h ) { case heightsync . ConfirmConfirmed : // commit verdict case heightsync . ConfirmPending : return InconclusivePendingHeight ( h ) case heightsync . ConfirmStale : return InconclusiveStaleOracle () }

15.2 Observed height (courier heartbeat)

func ( c * transport. HTTPClient ) ObservedHeightNow () ( uint64 , bool )

Returns (h, true) where h is the highest fresh tip in the courier peer-tip cache; (0, false) when no fresh tip exists or height sync is not configured. Used by cPoC C14 heartbeats — a false return means "Inconclusive — no fresh height".

15.3 Exculpation evidence (dispute layer)

// User side: produce the originator's signed blob for (originator, h). func ( c * transport. HTTPClient ) HeightSyncEvidenceFor ( originator string , h int64 , ) ( blob , sig [] byte , ok bool )

Returned by the courier cache ( HeightSyncPeerTips.OriginSignedBlobFor ). Verifiable with heightsync.VerifyOriginDetached(verifier, sec, blob, sig) without reaching the user.

15.4 Strong-grade evidence (Strong mode)

// Host side: return cached LightBlock for h, if available. func ( s * transport. Server ) LightBlockFor ( h int64 ) ( proof [] byte , ok bool )

When the receiver's follower has advanced past a disputed H , the dispute packet may carry both halves:

  • Originator's signed blob from HeightSyncEvidenceFor (blame).
  • Receiver's LightBlock from LightBlockFor (canonical pair).

A mock dispute verifier returns DISPUTE_ORIGINATOR when both pass.

15.5 Cold-start seed (optional)

// Server option: transport . WithHeightSyncSeedRPC ( true ) // Client call: func ( c * transport. HTTPClient ) SeedHeightSync ( ctx context. Context ) ( uint64 , bool , error )

Opt-in POST /sessions/:id/height-sync : the host returns a forced Anchor (originator-signed). The courier verifies + caches it before issuing the first inference — useful for short-lived sessions where the first inference is not in a sync turn.

15.6 Force a sync turn

// Operator / dispute / cPoC trigger: state . SendMsgForceHeightSyncTurn ( triggerNonce , slotsNum , reason , strongRequired )

Opens an ActiveForcedTurn in the next diff; every envelope in [trigger, trigger + slots_num − 1] MUST be Anchor (or Strong when strongRequired = true ).

15.7 Audit and dispute consumers

type AuditRing interface { List ( peerID string ) [] AnchorAttestation ListPeers () [] string ConfirmationView () ConfirmationView } func ( c * transport. HTTPClient ) HeightSyncAuditRing () * heightsync. AuditRing func ( s * transport. Server ) HeightSyncAuditRing () * heightsync. AuditRing

Used by dispute / finalization consumers that need verbatim attestations and per-peer history.

16. Attack model

Each row maps an adversary action to the protocol's defence and to the test scenario that proves it (full catalog in height-sync-tests.md (https://github.com/a-kuprin/gonka/blob/devshard-testenv/devshard/docs/height-sync-tests.md) ).

Out-of-scope adversaries:

  • An adversary who controls > 2/3 of mainnet validators — outside this protocol's defence; same as any L1 consensus assumption.
  • An adversary who poisons the host's local block oracle — block oracle has its own pinned validator-set verifier ( blockoracle/verifier ); height sync does not re-validate.

17. Defaults and configuration

Reading order for contributors

  • §6 — architecture diagram. Build a mental model of the host producer (own oracle, signs response leg) and the courier user (peer-tip cache, request-leg carrier) feeding a single receiver pipeline.
  • §8 — the three sync modes and the state diagram.
  • §11 — the receiver pipeline flowchart; this is the load-bearing normative section.
  • §12 — asymmetric signing model.
  • §14 + §15 — what cPoC actually consumes.
a-kuprin avatar
a-kuprinMaintainerMaintainer

Important point according to this proposal is that consensus part is out of scope and should be here: Finalization protocol (https://github.com/gonka-ai/gonka/discussions/1369)

It is the optimistic protocol - we trust while height is in some delta from our own known height. But we always have signed by originator block hash, so later, when we get same height from dapi, if we see block hash differs we can start the dispute, and originator of invalid data will be punished.

While document states that block hash could be also source of deterministic randomness (if we select height and related block hash in the future in advance), there are other possible solutions to get source of deterministic randomness, that could be more elegant and this is point of discussion

shd avatar

Random comments, part 1.

  • I'd propose to describe the "non-optimistic path" in detail. As I understand, this path leads to consensus round, but maybe this should be better described. In particular, "Strong (LightBlock + VerifyCommit)" - either references to the protocol are necessary, or its description should be provided. Existing description is too sketchy (at least to my outsider opinion).

Also I'd also propose to describe the usage of height/block_hash in more detail: maybe some improvements to the algorithm could become obvious after that.

  • Some random questions and ideas about D (maximal interval between adjacent heights):

... We trust heights in the future without additional mainnet proove if the height is close to the one we know is current. If there is a large disagreement between hosts on height (height_in_the_future - known_height = |Δ| > D) we use the full data from mainnet (block hash and validators signatures) to validate the height ...

What if the time difference between nonces is big enough? For example, 1 min? It will lead to large disagreement and to consensus round (even despite there is no real reason for a dispute: attack model 13, Long inter-block time (feed quiet, cached tip still valid) ). Imagine that inference requests are coming with 1 min intervals: each will be accompanied with a consensus.

However, if one would put big D as a measure against regular strong phases, then it can damage precision of height discovery.

We can have the following proposals here:

a) (Maybe this is wrong) but the height main usage is PoC phase detection: if a user requests inference, but the node replies with "rejected because of PoC". In this case, user immediately sends the request to the next node, and that reply comes immediately, and D is important. So, the check for D may be activated only on some occasions. Otherwise, we always trust height progression, unless the previous height is earlier than ours.

b) User may specify current time in request -- so Host must provide be closest block height to the user's time, maybe +-1 slot. In case user time is too different from host time, a consensus (strong phase) can be activated: hosts sends to everybody user's current time and his own current time. In case of 5 seconds consensus (comparable with consensus speed in mainnet) this mechanism can be efficient enough.

c) Adaptive up-to-date height guarantees. User makes empty requests each 5 seconds, it must do them (so that these requests would receive latest height in proper time intervals, and height changes would always be either 0 or 1) -- or performs a consensus in case of a long pause.

Consensus can be very long in comparison with these empty messages. These messages can be cast in separate round robin - that is, not affect next inferencing host. They can stop after (N^2/2) empty requests -- that is, stop right before price of liveness support becomes bigger than price of consensus after recovery.

  • Source of randomness: a) the proposed method leaves some space for manipulation (since height difference < D gives possibility of chosing best possible hash). b) in case of adversary host AND user, that possibility becomes guarantee (user and host together can always choose proper delay to receive necessary hash - e.g. to select proper next node) c) so, commitment schemes may be preferred here: if host and user are from "different teams" d) in case host AND user are adversary together, additional approaches can be proposed, the exact formulation is outside of the scope for the comment.
akup avatar
akupMaintainerMaintainer
2026-07-01

I think we will start another discussion according to source of randomness.

block hash could be source of randomness and rather natural when we have height sync protocol (that we anyway need for cPoC handling at devshard). But pederson-style source of randomness is very elegant, I think. Please write down that proposal

shd avatar

Important update about D: elementary analysis shows that there is a big increase in number of consensus rounds required at the border of 30 seconds/1 minute inactivity period.

Very approximate actual data suggestions: epoch 263 had 590000 inference requests per 44 users, which gives T = 0.15 requests per second. We can consider (as the first guess) that inference requests follow Poisson distribution, it gives e^(-0.15 * 60) = 0.0001 probability of 60 seconds inactivity; and e^(-0.15 * 30) = 0.01 probability of 30 seconds inactivity.

Of course, the distribution is different (requests are usually aligned to some inference process -- that is, they are not independent), and the exact numbers will be different. But D may influence performance in a very unusual way if moderate pause in inference leads to a new consensus round.

akup avatar
akupMaintainerMaintainer
2026-07-01

D here is not related to nonces/blocks it is related to mainnet block height.

If we are at height difference > D we should run the consensus round, and the block increment time of mainnet is rather stable around 5 seconds, but on heigh load at winter we have seen slow block building when one block was incrementing around 30-45 seconds

shd avatar
shdMaintainerMaintainer
2026-06-22

Question: There is an attack vector, attack model 13, Long inter-block time (feed quiet, cached tip still valid) However, is there any specific attack/case about long inactivity of user?

akup avatar
akupMaintainerMaintainer

It was described separately at cPoC protocol proposal. It was referenced from this doc at github repos, but I've just published it as a discussion: #1384 (https://github.com/gonka-ai/gonka/discussions/1384)

There are Cases to handle paragraph and especially C14 — Low-load strategic delay (developer heartbeat mitigation) discussing exactly this long inactivity of user

As we discussed at DM, the proposal to mitigate such attack is heartbeating from the user, and also we should handle that this heartbeating is required at protocol level and if user stops heartbeat we should autosettle the shard.

shd avatar

Generating random numbers

Generation of random numbers from blockchain hash of the most recent main net block has a lot of advantages, however, it has the following problems:

  • it does not allow to generate the number "on spot" --- it requires delay till the new main net block arrives.

it does not allow to generate the number "on spot" --- it requires delay till the new main net block arrives.

  • synchronization delays: a generating party has some possibility to choose from several headers (in the range of D consecutive headers), and therefore has some influence on the number.

synchronization delays: a generating party has some possibility to choose from several headers (in the range of D consecutive headers), and therefore has some influence on the number.

The both problems can be addressed, but that comes with price, so it's a good idea to consider different approaches.

Alternative approach

As a simple alternative approach, we'd propose to use a standard commitment scheme. Let's take some reliable hash function (e.g., sha256) --- let's call it F(x), where x is a binary string.

Then a random number generation (for user U and host H) could follow the following scheme:

  • Each party (U and H) generates one random number --- let it be R_U and R_H. It should be a long number, e.g. 128 or 256 bits, to prevent guessing of the number.

Each party (U and H) generates one random number --- let it be R_U and R_H. It should be a long number, e.g. 128 or 256 bits, to prevent guessing of the number.

  • Then the parties exchanges hashes of the number --- H sends F(R_H) to U, and U sends F(R_U) to H.

Then the parties exchanges hashes of the number --- H sends F(R_H) to U, and U sends F(R_U) to H.

  • After the values are exchanged, the parties exchange the original random numbers: so each of the parties knows R_U, R_H, F(R_U) and F(R_H).

After the values are exchanged, the parties exchange the original random numbers: so each of the parties knows R_U, R_H, F(R_U) and F(R_H).

  • The value Xor(R_U,R_H) is the desired random value.

The value Xor(R_U,R_H) is the desired random value.

What are the properties of this protocol, its security analysis

  • If numbers R_U and R_H are generated without knowledge of each other, and at least one of the parties generated it randomly, then the resulting value is also random.

If numbers R_U and R_H are generated without knowledge of each other, and at least one of the parties generated it randomly, then the resulting value is also random.

  • Values F(R_U) and F(R_H) do not disclose any information about R_U, R_H, although brute force attack may be possible in case of short numbers (that's why we have requirement of 128 or 256 bits).

Values F(R_U) and F(R_H) do not disclose any information about R_U, R_H, although brute force attack may be possible in case of short numbers (that's why we have requirement of 128 or 256 bits).

  • The parties cannot change their mind after hashes are exchanged.

The parties cannot change their mind after hashes are exchanged.

Hash collisions

Sha256 is still considered to be resistant to collisions, however, for the completeness of the text, it is necessary to note that this scheme can be made more resistant to such attacks even if such collisions are found (or a weaker hash function is used).

For example, one has a pair of numbers, which share the same hash, but have different values: A,B such that F(A) = F(B). So, at the moment of the numbers reveal, an adversary party may have a choice (either A or B), which may lead to some control over the resulting random number.

To prevent this, one can prepend the scheme with exchange of nonces:

  • Parties exchange two random numbers (N_U and N_U respectively) and the random numbers being generated at step 1 should include these numbers as prefixes: R'_U = N_H ++ R_U, and R'_H = N_U ++ R_H, where ++ is concatenation of binary strings.

This modification should prohibit another party from use of previously computed collisions in hash functions. The need for this complication of the randomness generation is debatable.

General analysis

  • This scheme is secure, and resistant to adversary behavior of the parties, and generates good random if at least one of the sides is interested in that.

This scheme is secure, and resistant to adversary behavior of the parties, and generates good random if at least one of the sides is interested in that.

  • This scheme can be activated on-demand at any moment, and it's rather cheap. Although, it requires 2 elementary messages (or 3, in more secure variant) from each party.

This scheme can be activated on-demand at any moment, and it's rather cheap. Although, it requires 2 elementary messages (or 3, in more secure variant) from each party.

  • However, it does not resist cooperative random generation: when both U and H are together trying to forge a desired number. The blockchain hash scheme also has this problem to some extent.

However, it does not resist cooperative random generation: when both U and H are together trying to forge a desired number. The blockchain hash scheme also has this problem to some extent.

Cooperation-resistant inference verification (draft proposal)

The need of multiple-host devshards is dictated by the need to distribute control over the inference process, and make it transparent and honest. However, cooperative behavior of the user and some of the hosts may damage the guarantees. In particular, such cooperation may easily assign verification of compromised inferences to adversarial hosts, thus damaging the whole scheme.

To prevent this from happening, the following idea is proposed.

  • Each host must keep the last 32 (by the size of devshard) inferences, ready for future verification. The actual verifier and the actual inference which will be verified are not known to anyone at this point.

Each host must keep the last 32 (by the size of devshard) inferences, ready for future verification. The actual verifier and the actual inference which will be verified are not known to anyone at this point.

  • The host and the verifier for a given inference are determined by a random number generation.

The host and the verifier for a given inference are determined by a random number generation.

  • Then the verifier asks the inferencing host its past task (one of 32) and performs necessary verification. If the random number is not always generated by adversary parties, and at least sometimes (in fact, in 2/3 cases) is really random, it gives a good guarantees against cheating.

Then the verifier asks the inferencing host its past task (one of 32) and performs necessary verification. If the random number is not always generated by adversary parties, and at least sometimes (in fact, in 2/3 cases) is really random, it gives a good guarantees against cheating.

This idea does not solve all problems with honesty of user and hosts, but it gives additional layer of protection.

The main question here is how to generate random numbers. The text below explains that.

Random number generation

If the user and the host are not from the same adversary teams, then the commitment random generation scheme above already gives a good algorithm for that. The main issue is how to add this non-adversary party into random number generation, if the user and the host are from the same team.

Imagine, user U with host H generate a number, and this number determines the verifier (and they abuse the protocol to select the verifier from their team). It guides us to conclusion that the number should be generated by all other hosts in the devshard. For example, it could be consecutive generation: for 1st inference of H_0 the number is generated by U and H_1. For the 2nd inference of H_0 -- by U and H_2, and so on. So, after 32 rounds (or, 32 inferences by each host) at least 20 of them will be randomly selected by non-cooperative party.

So, we should have two round-robin cycles: an inference cycle (all nodes are requested to make inferences consequently, to minimize room for node shadowing), and verification cycle.

The particular details are yet to be determined, but the main idea that this cycle should deliberately go in different speed (in comparison with inference cycle). So that during one inference cycle (so that inference starting from H_0 returned back to H_0), the verification cycle hosts should be shifted (verificatoin.H_0 with inference.H0 -- they start at the same host, but at the moment of inference returning back to H_0, the verification should be at H_k with k /= 0).

Since there are 2/3 honest hosts in the network, then in 2 of 3 cycles a host will be paired with a host, which is not in his adversary team. And after 11 cycles each host will have at least one verification, controlled by an independent host.

The inference cycle can be light-weight (that is, it may have no independent blockchain), and all the transactions may be added to the same diff sequence; the two cycles may be interlaced in the single devshard blockchain in determenistic fashion.

Also, this verification cycle can be used for block height propagation (to avoid frequent consensus invocation).

a-kuprin avatar
a-kuprinMaintainerMaintainer
2026-07-02

I think this Pederson-commitment-style for source of randomness is best option for finalization (https://github.com/gonka-ai/gonka/discussions/1369) selecting the collector for the commitment.

Anyway we should revisit the finalization protocol as BLS fits there very well as we discussed