`devshard` `technical` Height-sync protocol

Протокол синхронизации по высоте
В настоящее время в devshard отсутствует децентрализованный оракул по высоте блока, что можно доказать на уровне консенсуса devshardd. Это критически важно для обработки cPoC на devshardd без наказания хостов за пропущенные скорости и за заранее неизвестную детерминированную случайность, необходимую для обновления протокола проверки.
Эта часть протокола очень важна, но она является лишь строительным блоком для cPoC и проверки.
Соответствующий пиар находится здесь (https://github.com/gonka-ai/gonka/pull/1209)
Конверты пользователя ↔ хоста содержат дополнительный HeightSyncSection, который подтверждает наличие пары основной сети (height, block_hash). Этот раздел является единственным вводом для межузлового выравнивания времени, принятия решений о тайм-ауте и предиката IsStrictlyConfirmed(h), который выносит вердикт нисходящим протоколам (cPoC, финализация). block_hash — источник детерменистической случайности, который заранее неизвестен, используется VALIDATION_PROTOCOL_PROPOSAL.md (https://github.com/a-kuprin/gonka/blob/1f0933ad9136cfbcf7070f8210e2c6694731ebaf/devshard/docs/proposals/VALIDATION_PROTOCOL_PROPOSAL.md)
Этот документ является канонической спецификацией одной версии. Реализация в дереве соответствует этому документу; в каталоге тестов (height-sync-tests.md (https://github.com/a-kuprin/gonka/blob/1f0933ad9136cfbcf7070f8210e2c6694731ebaf/devshard/docs/height-sync-tests.md)) перечислено, что уже доказано и что планируется.
Связанные документы: height-sync-tests.md (https://github.com/a-kuprin/gonka/blob/1f0933ad9136cfbcf7070f8210e2c6694731ebaf/devshard/docs/height-sync-tests.md) (каталог тестов — реализован и запланирован), CPOC_PROTOCOL.md (https://github.com/a-kuprin/gonka/blob/1f0933ad9136cfbcf7070f8210e2c6694731ebaf/devshard/docs/proposals/CPOC_PROTOCOL.md), FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md (./FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md) , VALIDATION_PROTOCOL_PROPOSAL.md (https://github.com/a-kuprin/gonka/blob/1f0933ad9136cfbcf7070f8210e2c6694731ebaf/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 sync Turn] 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 -- нет/локально-отсутствует --> 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 (../height-sync-tests.md)).
Внешние противники:
- Злоумышленник, который контролирует > 2/3 валидаторов основной сети — вне защиты этого протокола; то же, что и любое консенсусное предположение L1.
- Злоумышленник, который отравляет локальный оракул блока хоста — оракул блока имеет собственный закрепленный верификатор набора валидаторов (blockoracle/verifier); синхронизация высоты не проверяется повторно.
17. Значения по умолчанию и конфигурация

Height-sync protocol
Currently devshard is missing decentralized block height oracle, that can is provable in a devshardd consensus level. It is critical for handling cPoC at devshardd without punishing hosts for missed rates, and for deterministic randomness unknown in advance, needed for validation protocol update.
This protocol part is very important but just a building block for cPoC and validation.
Related PR is here (https://github.com/gonka-ai/gonka/pull/1209)
User ↔ host envelopes carry an optional HeightSyncSection that attests to a mainnet (height, block_hash) pair. 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 is a source of determenistic randomness that is unknown in advance, used by VALIDATION_PROTOCOL_PROPOSAL.md (https://github.com/a-kuprin/gonka/blob/1f0933ad9136cfbcf7070f8210e2c6694731ebaf/devshard/docs/proposals/VALIDATION_PROTOCOL_PROPOSAL.md)
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/1f0933ad9136cfbcf7070f8210e2c6694731ebaf/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/1f0933ad9136cfbcf7070f8210e2c6694731ebaf/devshard/docs/height-sync-tests.md) (test catalog — implemented and planned), CPOC_PROTOCOL.md (https://github.com/a-kuprin/gonka/blob/1f0933ad9136cfbcf7070f8210e2c6694731ebaf/devshard/docs/proposals/CPOC_PROTOCOL.md) , FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md (./FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md) , VALIDATION_PROTOCOL_PROPOSAL.md (https://github.com/a-kuprin/gonka/blob/1f0933ad9136cfbcf7070f8210e2c6694731ebaf/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 (../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.
Протокол синхронизации по высоте
В настоящее время в devshard отсутствует децентрализованный оракул по высоте блока, что можно доказать на уровне консенсуса devshardd. Это критически важно для обработки cPoC на devshardd без наказания хостов за пропущенные скорости и за заранее неизвестную детерминированную случайность, необходимую для обновления протокола проверки.
Эта часть протокола очень важна, но она является лишь строительным блоком для cPoC и проверки.
Соответствующий пиар находится здесь (https://github.com/gonka-ai/gonka/pull/1209)
Конверты пользователя ↔ хоста содержат дополнительный HeightSyncSection, который подтверждает наличие пары основной сети (height, block_hash). Этот раздел является единственным вводом для межузлового выравнивания времени, принятия решений о тайм-ауте и предиката IsStrictlyConfirmed(h), который выносит вердикт нисходящим протоколам (cPoC, финализация). block_hash — источник детерменистической случайности, который заранее неизвестен, используется VALIDATION_PROTOCOL_PROPOSAL.md (https://github.com/a-kuprin/gonka/blob/1f0933ad9136cfbcf7070f8210e2c6694731ebaf/devshard/docs/proposals/VALIDATION_PROTOCOL_PROPOSAL.md)
Этот документ является канонической спецификацией одной версии. Реализация в дереве соответствует этому документу; в каталоге тестов (height-sync-tests.md (https://github.com/a-kuprin/gonka/blob/1f0933ad9136cfbcf7070f8210e2c6694731ebaf/devshard/docs/height-sync-tests.md)) перечислено, что уже доказано и что планируется.
Связанные документы: height-sync-tests.md (https://github.com/a-kuprin/gonka/blob/1f0933ad9136cfbcf7070f8210e2c6694731ebaf/devshard/docs/height-sync-tests.md) (каталог тестов — реализован и запланирован), CPOC_PROTOCOL.md (https://github.com/a-kuprin/gonka/blob/1f0933ad9136cfbcf7070f8210e2c6694731ebaf/devshard/docs/proposals/CPOC_PROTOCOL.md), FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md (./FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md) , VALIDATION_PROTOCOL_PROPOSAL.md (https://github.com/a-kuprin/gonka/blob/1f0933ad9136cfbcf7070f8210e2c6694731ebaf/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, состоящее из двух разделов:
Раздел 1 выдается только при необходимости:
Хосты подписывают якоря ответной ветви своим ключом подписывающего лица secp256k1; пользователи-курьеры переносят эти подписанные BLOB-объекты вперед, проверяя при приеме и используя их в качестве доказательства невиновности по требованию. Якорям ветки запроса доверяют хосты (без встроенной подписи) — в случае спора пользователь доказывает происхождение позже.
Одиночный предикат IsStrictlyConfirmed(h) предоставляет нижестоящим потребителям дискретный ответ {подтвержденный, ожидающий, устаревший}.
2. Проблема
В devshard нам нужен источник высоты и случайности основной сети, который заранее неизвестен, но является детерминированным, чтобы заставить все хосты и пользователя согласиться.
Каждый хост имеет свой собственный последний (высота, block_hash) оракул (цепочка вывода grpc) и подписи валидаторов цепочки вывода. Но хосты должны согласиться с тем, что любая высота, указанная в протоколе, действительно является последней. Таким образом, в этом документе должен быть указан протокол синхронизации по высоте.
3. Общий обзор протокола
Поскольку devshard рассчитан на высокую пропускную способность, мы стремимся свести к минимуму дополнительные данные, передаваемые в сообщениях, и свести к минимуму проверки подписей Minnet. Также мы минимизируем сплетни, которые должны возникать только в случае споров и урегулирования, чтобы минимизировать трафик (поскольку один хост может находиться во многих devshard'ах).
Итак, основные дизайнерские решения приняты:
В результате мы предоставляем API для devshardd и devshardctl, который выдает последнюю высоту, хэш блока и информацию, если с этим согласны большинство участников сети devshard.
4. Цели
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Ключевые инварианты:
7. Формат провода
HeightSyncSection передается как поле protobuf в конверте вывода и зеркально отображается в формате JSON для инструментов. Номера полей стабильны.
Примечания:
Зеркало JSON:
( Tip_stale_after_ms опускается, если кэшированная подсказка свежая.)
8. Режимы синхронизации (пропустить/привязать/сильный)
stateDiagram-v2 направление LR [*] --> Пропустить Пропустить --> Привязка: одноразовый номер при синхронном повороте/принудительном повороте/ленивом переносе Привязка --> Пропустить: следующий одноразовый номер за пределами окна Привязка --> Строгая: \|H − local_aligned\| > D ИЛИ принудительное (StrongRequired) Сильное --> Привязка: партнеры перестроены, снова внутри D Привязка --> Привязка: частота шагов на следующем ходу Сильная --> Сильная: неподвижно > DПериодическое выравнивание использует только Anchor. Сильный – это не шаг по умолчанию, а путь разногласий/споров.
Тихий канал против мертвого канала (хосты):
9. Каденс
Синхронно-поворотные окна
Для направления сеанса для исходящего nonce n:
Ограничение: 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 на этапе запроса iff:
Получатель классифицирует это как VALID_LAZY_ANCHOR (тег аудита lazy ); он не открывает обязательство синхронизации-поворота.
10. Правила продюсера
Хосты (есть собственный оракул)
Пользователь-курьер (нет собственного оракула)
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 sync Turn] 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 -- нет/локально-отсутствует --> L[отложенная проверка постановки в очередь] J -- локальное И несоответствие --> M{присутствует отправитель?} M -- да --> M1[DISPUTE_ORIGINATOR] M -- нет --> M2[DISPUTE_CARRIER]Нормативные шаги для конверта без пропуска:
Классы результатов
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Этап ответа (хост → пользователь)
Ветка запроса (пользователь → хост)
Оправдание
Если позже хост открывает спор против пользователя-перевозчика, пользователь вызывает HTTPClient.HeightSyncEvidenceFor(originator, H) для создания кэшированного (blob, sig) . Средство проверки спора повторно запускает VerifyOrigin ; успех ⇒ вина переносится на исходный хост ( DISPUTE_ORIGINATOR ); неудача ⇒ вина остается на перевозчике ( DISPUTE_CARRIER ).
Сильное доказательство
Когда Strong находится на связи (light_block не пуст), проверка является криптографической по отношению к закрепленному набору валидаторов:
13. Перенос, происхождение, атрибуция
Правила
последняя_пропагированная дисциплина
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 }Семантика:
Монотонность: однажды подтвержденная высота остается подтвержденной. ожидание → подтверждено — единственный переход вперед.
Правила подтверждения
Настраивается во время развертывания:
Развертывания PoC без Strong run (C-quorum). При развертывании производственного класса СЛЕДУЕТ выбрать (C-гибрид) после включения Strong.
Память подтверждений и обрезка
Индивидуальный просмотр, а не глобальный
IsStrictlyConfirmed вычисляется по собственному кольцу и часам аудита вызывающего абонента. Два верификатора могут временно не согласиться (ожидание или подтверждение); Срезка на основе кворума cPoC допускает это.
15. Интеграция cPoC — полный API
Следующие API Go представляют собой стабильную поверхность, которую используют cPoC и финализация. Пути реализации указаны в скобках.
15.1 Дискретный предикат подтверждения
Оба предоставляют ConfirmationView.IsStrictlyConfirmed(h uint64) ConfirmState . cPoC §C6 / §C14 / §Вердикт, шаг 5, позвоните напрямую.
Пример использования (вердикт cPoC):
15.2 Наблюдаемая высота (пульс курьера)
Возвращает (h, true), где h — самая высокая свежая подсказка в кэше одноранговых подсказок курьера; (0, ложь), когда нет свежего наконечника или синхронизация высоты не настроена. Используется пульсом cPoC C14 — ложный возврат означает «Неубедительно — нет свежей высоты».
15.3 Доказательства, оправдывающие вину (уровень спора)
Возвращается кэшем курьера ( HeightSyncPeerTips.OriginSignedBlobFor ). Можно проверить с помощью heightsync.VerifyOriginDetached(verifier, sec, blob, sig) без доступа к пользователю.
15.4 Доказательства сильной степени (сильный режим)
Когда ведомый получателя прошел мимо спорного H , спорный пакет может содержать обе половины:
Верификатор ложного спора возвращает DISPUTE_ORIGINATOR, когда оба пройдены.
15.5 Семена холодного запуска (опция)
Согласие POST /sessions/:id/height-sync : хост возвращает принудительную привязку (подписанную отправителем). Курьер проверяет + кэширует его перед выдачей первого вывода — полезно для кратковременных сеансов, когда первый вывод не выполняется в ходе синхронизации.
15.6 Принудительный синхронный поворот
Открывает ActiveForcedTurn в следующем диффе; каждый конверт в [trigger, Trigger + slots_num − 1] ДОЛЖЕН быть Anchor (или Strong, если StrongRequired = true ).
15.7 Аудит и споры с потребителями
Используется потребителями споров/завершений, которым необходимы дословные подтверждения и история каждого узла.
16. Модель атаки
Каждая строка отображает действия злоумышленника на защиту протокола и на тестовый сценарий, который это доказывает (полный каталог в файле height-sync-tests.md (../height-sync-tests.md)).
Внешние противники:
17. Значения по умолчанию и конфигурация