`devshard improvements` cPoC skip protocol — предложение
Оригинал: `devshard improvements` cPoC skip protocol — proposal

протокол пропуска cPoC (devshard) — предложение
Резюме
В devshard хосты, на которых выполняется PoC подтверждения (cPoC), не должны обслуживать обычный вывод в течение срока действия своих обязательств PoC. Другие хосты должны иметь возможность определить, является ли пропуск законным (пропускающий хост находится в расписании cPoC на соответствующей высоте) или неправомерным (лжет или отказывается от работы). Этот документ определяет поток данных и случаи, которые должен обрабатывать протокол cPoC.
Вне области применения этого документа:
- Как каждый хост получает/согласовывает высоту основной сети. Это решается с помощью HEIGHT_SYNC_PROTOCOL_PROPOSAL.md (./HEIGHT_SYNC_PROTOCOL_PROPOSAL.md) (Omit/Anchor/Strong, отложенные проверки и т.д.). Здесь мы предполагаем, что каждый хост имеет скаляр **H(host)**, равный высоте, известной большинству хостов валидаторов/devshard (его собственные правила последователя + синхронизации высоты сошлись на этом значении). Несоответствия на уровне, регулируемом спецификацией синхронизации по высоте, являются проблемой этой спецификации; этот документ различает только случаи, когда такое несоответствие влияет на вердикт cPoC, и откладывает само несоответствие на синхронизацию по высоте.
- Выбор хостов POC_SLOT (роль освобождения от вывода) — политика/RNG, см. таблицу областей применения.
- Математика расчетов/сокращений в основной сети — выходит за рамки; этот документ выдает вердикты (действительный/недействительный/неокончательный) и передает доказательства FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md (./FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md).
Статус: черновик — поток данных + случаи, указанные ниже; схемы проводов, цепные крючки и предикаты разрезания еще не определены.
Область применения
Общие предположения (информативно)
- Оракул высоты (предоставляется синхронизацией высоты, здесь рассматривается как черный ящик): каждый хост **V ** предоставляет скаляр **H(V)** — высоту основной сети, известную большинству валидаторов/хостов devshard на момент последней конвергенции **V со слоем синхронизации высоты. В этом документе не уточняется, как вычисляется, доверяется или обновляется H(V); см. HEIGHT_SYNC_PROTOCOL_PROPOSAL.md (./HEIGHT_SYNC_PROTOCOL_PROPOSAL.md) . Когда в этом документе без уточнений говорится «высота **H», читайте это как **H(V) в момент, когда V оценивает случай**.
- Расписание cPoC: Учитывая хост **H_i ** и высоту основной сети **H** , существует детерминированный предикат **Schedule(H_i, H) ∈ {idle, подготовить, активный}**, который может быть получен из состояния цепочки/эпохи любым человеком с такой высотой. Семантика подготовки и активности определена в спецификации cPoC на стороне цепочки и выходит за рамки данной статьи.
- **Список POC_SLOT:** Для действующей эпохи набор **PoC_slot_set ** хостов с POC_SLOT = true доступен каждому честному хосту (точное происхождение — инициализация условного депонирования или запрос после инициализации — является открытым вопросом 1).
- Расписание исполнителя: запросы в сеансе упорядочиваются монотонным одноразовым номером (линейным приращением). При использовании слотов **N_slots ** и фиксированного сопоставления **executor(nonce) = hosts[nonce mod N_slots]** один и тот же логический слот повторяется в **nonce + N_slots** (один раунд).
- Асинхронный трафик разработчика: разработчик не ждет ответа хоста перед отправкой следующего запроса. Ответ на запрос **R_req ** объединяется с линеаризованным разницей сеанса через какой-то более поздний одноразовый номер **R_req + x **, **x ≥ 0** — не обязательно тот же одноразовый номер. Любое правило с привязкой к одноразовому номеру должно работать с тем одноразовым номером, в котором сообщение появляется в Diff , а не с сопряжением настенных часов с исходящим запросом.
Обозначения (одноразовые номера, используемые повсюду)
Все одноразовые номера ниже являются монотонными индексами в Diff (поток данных § локальное состояние для каждого сеанса). Они определены здесь один раз, чтобы в последующих разделах можно было ссылаться на них без повторного введения каждого из них.
Постановка задачи
1. Пропустить корректность
Хост, который возвращает «пропуск из-за cPoC», может быть:
Честный — Schedule(H_i, H) ∈ {prepare, active} и H_i ∉ PoC_slot_set, или
- Вредоносный — возвращает CPoC_SKIP, пока он не запланирован/находится в PoC_slot_set (избегает работы).
Протокол должен позволять каждому честному верификатору **V ** выносить один и тот же вердикт из одного и того же diff, используя **H(V) ** в качестве оракула высоты (предположение 1).
2. Разработчик воспроизводит/удерживает
Разработчик может сохранить ответ пропуска cPoC хоста, а затем прикрепить его через CarrySkip. Смягчение является многоуровневым:
- На уровне вердикта cPoC (этот документ): актуальность ограничена высотой основной сети, а не раундами, через интервал I = [h_X, h_carry], который лично подтверждает каждый верификатор (§ Nonce-привязка). Позднее перенос настоящего пропущенного пятна остается действительным — оно было правдивым на высоте в I; позднее раскрытие информации задним числом не означает, что это ложь. Только пропуски, которые никогда не были законными на любой высоте, я создаю Invalid .
- На уровне расчета (здесь выходит за рамки): оставшийся ущерб от позднего переноса — записи выводов остаются открытыми, устаревшие доказательства, используемые для остановки расчета — обрабатываются с помощью тайм-аутов MsgTimeoutInference{…CPOC} и сроков завершения.
3. Объем сплетен
При высокой скорости вывода, если большинство хостов пропускают время cPoC, сплетни о каждом пропуске неприемлемы:
- Никаких сплетен в обычном раунде, если различия уже распространяют доказательства.
- Доказательства спорного уровня основаны на финализации/совместном использовании состояния (./FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md), а не на параллельном канале рассылки.
Принципы проектирования (высокий уровень)
Формализация в § Поток данных и случаи в § Обрабатываемые случаи выбраны так, чтобы удовлетворить следующим принципам. Символы Nonce (R_req, N_SP, N_carry, X, N_slots) определены в разделе Общие предположения → Нотация; H(V) , Schedule и PoC_slot_set в пунктах 1–3 общих предположений; timeout_skip_gossip в разделе § Минимизация сплетен.
Два пути запроса
При открытии запроса разработчик выбирает одну из двух форм; оба сходятся к одному и тому же предикату Verdict.
Путь А — вывод с возможным отказом от cPoC (полная полезная нагрузка). Разработчик отправляет реальный запрос на вывод; хост либо подтверждает и запускает его, либо отказывается из-за cPoC.
D → devshard : MsgStartInference(R_req, Prompt_hash, …) [в Diff в R_req] счастливый путь → H_i → devshard : MsgConfirmStart(R_req) [в Diff] → … → MsgFinishInference путь cPoC → H_i → D : CPoCSkipResponse(R_req, причина) [p2p; НЕ в Diff] D → devshard : CarrySkip(N_carry, <встроенный CPoCskipResponse>) [в Diff в N_carry]
Путь B — облегченная проверка пропуска (без подсказки, без затрат на вывод). Разработчик просит H_i сообщить о своем состоянии cPoC, не платя подсказки. Хост не выполняет вывод; он просто возвращает подписанный статус. Ответ имеет два возможных результата:
- Отказ — H_i все еще находится на cPoC (cpoc_active или cpoc_prepare); ведет себя как пропуск Path-A для вынесения вердикта.
- Готов — H_i завершил cPoC и находится в состоянии READY_INFERENCE ; разработчик должен возобновить отправку настоящего MsgStartInference в H_i.
D → devshard : MsgSkipProbe(N_SP) [в Diff в N_SP] H_i → D : CPoCProbeResponse(N_SP, результат ∈ {cpoc_active, cpoc_prepare, готов}) [p2p; НЕ в Diff] D → devshard : CarrySkip(N_carry, <embedded CPoCProbeResponse>) [into Diff at N_carry] # N_SP < N_carry строго (две отдельные записи Diff)
Готовый результат, перенесенный в Diff, не является пропуском Invalid — предикат вердикта просто не применяется (отсутствие отказа в проверке). Вместо этого это уведомление о планировании: V записывает, что H_i сообщил о готовности на высоте в [h_X, h_carry] . Последующее поведение разработчика проверяется по этому получению с помощью C13 (разработчик продолжает проверять/пропускать готовый хост — см. § Случаи).
Будущая оптимизация (отложенная, см. Открытый вопрос §8). Как только D получит новый CarrySkip, доказывающий, что H_i находится в cPoC, последующие пропуски H_i в том же окне cPoC не должны требовать полного обхода проверки: D может поместить один подписанный разработчиком маркер в Diff в слоте H_i и направить реальный запрос на H_{i+1} . Сворачивает тройку Path-B из трех сообщений ( MsgSkipProbe → CPoCProbeResponse → CarrySkip ) до одной записи со знаком D при каждом повторном пропуске. Вне возможностей этого выпуска; текущий документ определяет только поток явного зондирования.
Ключевые инварианты, общие для обоих путей:
- Ответы хоста являются P2P и не могут быть непосредственно наблюдаемы верификаторами. Будь то CPoCskipResponse (отказ пути A) или CPoCProbeResponse (статус пути B), подписанный оператор хоста попадает в поле зрения проверяющего только тогда, когда разработчик повторяет его через **CarrySkip ** в Diff .
- **CarrySkip — это основной артефакт, несущий вердикт в Diff.** Каждый V вычисляет Verdict из Diff[N_carry] . Для пути A предикат дополнительно сканирует Diff на наличие MsgConfirmStart, соответствующего тому же inference_id; если он существует (в любом направлении относительно N_carry ), вердикт является недействительным в отношении H_i для двойного утверждения (см. § Предикат вердикта, шаг 2 и § Случаи → C2').
- Две отдельные записи Diff для каждого пути. Оба пути имеют R_req < N_carry (соответственно. N_SP < N_carry ) строго: разные прото-сообщения занимают разные одноразовые номера, а подпись разработчика в CarrySkip связывает байты, которые существуют только после прибытия p2p-ответа хоста.
- Расчет развязан. Как только CarrySkip достигнет окончательного Verdict , закрытие записи вывода на уровне цепочки использует существующий путь MsgTimeoutInference{reason = TIMEOUT_REASON_CPOC} (новое значение перечисления); этот шаг урегулирования не является тем, от чего зависит приговор.
Все нижеприведенное — привязка nonce, минимизация сплетен, поток данных, случаи — одинаково применимо к обоим путям; единственная специализация пути B — это дополнительный готовый результат (и его последующий случай C13).
Nonce-привязка (свежесть в интервале высоты)
Из-за асинхронного трафика разработчиков (Общие предположения, пункт 5) ответ на запрос, отправленный в nonce **R_req **, может появиться в Diff только в nonce **R_req + x** , **x ≥ 0** . Задержка x не ограничена количеством раундов — раунды могут быть намного быстрее, чем блоки основной сети или ответ хоста, поэтому между R_req и N_carry может пройти много раундов. Таким образом, вердикты привязываются к интервалу высот, который каждый верификатор конструирует локально на основе своих собственных наблюдений за Diff :
- Ссылочный nonce подтверждения пропуска = **R_req ** (запрос, на который он отвечает), указанный внутри подписанного CPoCskipResponse . (Термин выбран во избежание столкновения с «Якорем» синхронизации по высоте, который здесь выходит за рамки.)
- Carry nonce = **N_carry ** (nonce, в котором CarrySkip добавляется к Diff и становится видимым для проверяющих).
- Свидетельствующий nonce X = последний nonce ≤ R_req, исполнителем которого является сам V — nonce V, обрабатываемый лично, поэтому height_at[X] — это высота V, фактически наблюдаемая не позднее R_req . Точная формула (тот же раунд по сравнению с предыдущим раундом, в зависимости от SP_v по сравнению с предыдущим раундом). SP_e ) и его вывод приведены в § Предикат Вердикта, шаг 1.
- Интервал высоты I = [h_X, h_carry] где h_X := height_at[X] и h_carry := height_at[N_carry] = H(V) при приеме Diff[N_carry] . Этот интервал ограничивает набор высот основной сети, на которых физически мог быть произведен пропуск хоста, как видно через локальные часы этого верификатора.
- Проверка легитимности (античит, а не антиповтор). Пропуск является законным тогда и только тогда, когда ∃ H ∈ I : Schedule(H_i, H) ∈ {prepare, active} . Если ни одна высота в I не помещает H_i в расписание cPoC, пропуск не мог быть правдивым в любой момент, когда V был свидетелем → Недействителен для H_i . Устаревший, но настоящий BLOB-объект пропуска, воспроизведенный спустя много времени после того, как хост вернулся в READY_INFERENCE, все еще действителен — хост законно отказывался на некоторой высоте в I ; позднее перенос не делает это задним числом ложью. (Повторное воспроизведение/отказ от уплаты вреда вредит урегулированию, а не вердикту cPoC — см. § Консенсус/голосование и строку только для урегулирования в таблице примитивов.)
- Атрибуция высоты является только локальной. Каждый верификатор вычисляет h_X и h_carry на основе своей собственной карты height_at[·]; Заявленная высота разработчика или хоста в CPoCskipResponse является информационной и не учитывается при вынесении вердикта.
Минимизация сплетен
- Исключение на основе раунда (высокая нагрузка): если в течение времени out_skip_gossip после N_carry сеанс переходит к N_carry + N_slots (один полный раунд), каждый честный проверяющий видел доказательства через diff. Никаких специальных сплетен не распространяется.
- Сплетни на основе тайм-аута (низкая нагрузка): В противном случае любой V с недействительным вердиктом МОЖЕТ выдать компактный SkipEvidenceGossip, указывающий на Diff . Пиры повторно запускают предикат вердикта локально.
- Согласование окончательной обработки: глобальные доказательства спорного уровня используются с помощью FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md (./FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md), а не с помощью параллельного канала флуда.
Параметр **timeout_skip_gossip ** (предложение: ≈ 2 блока основной сети) параметризуется цепочкой; его точное значение здесь выходит за рамки.
Поток данных (формализованный)
Вечеринки
Локальное состояние для каждого сеанса (на каждом **V** )
Примитивы
Имена в subnet/proto/subnet/v1/{tx,diff}.proto, если не отмечено (новое). Входной набор предиката вердикта — MsgStartInference, MsgConfirmStart, MsgSkipProbe и CarrySkip; входной набор для расчета вердикта — CPoCVote; остальные сообщения являются носителями p2p ( CPoCSkipResponse , CPoCProbeResponse ), сообщениями о доставке ( SkipEvidenceGossip ) или окончательным расчетом ( MsgTimeoutInference{…CPOC} ).
Сквозной поток (счастливый путь, фактически хост на cPoC)
nonce R_req nonce R_req+1..N_carry-1 D ────────────── InferenceRequest(R_req) ─────────────▶ H_i (другие запросы к H_{i+1..}) │ H_i в cPoC │ D ◀─────────── CPoCSkipResponse(R_req, причина) ──────────┘ (приходит асинхронно, R_req + x в Diff) D ───── CarrySkip(N_carry) встраивает CPoCSkipResponse(R_req, …) ──▶ любой хост ──▶ Diff[N_carry] каждый V наблюдает за L: при приеме Diff[N_carry]: запись height_at[N_carry] = H(V) оцените Verdict(…) с использованием H(V) и правил nonce-окна ниже
Предикат вердикта (нормативная форма)
V вычисляет **Вердикт(skip_evidence) ∈ {Действительный, Недействительный, Неубедительный}** как:
- Причинность и построение интервалов высот. Применяется к первому CarrySkip в Diff, который ссылается на R_req (см. «Правило первого переноса» ниже):
- Причинность: R_req ≤ N_carry. CarrySkip не может ссылаться на запрос, который еще не вошел в Diff. Ошибка → Недействительно для оператора связи (подпись разработчика на CarrySkip), но не для H_i.
- Свидетельство nonce X (согласно § Принципы проектирования → Привязка Nonce). Пусть SP_e = R_req mod N_slots, SP_v = v_slot, round(R_req) = ⌊R_req/N_slots⌋. Тогда: Если SP_v ≤ SP_e → X = round(R_req) · N_slots + SP_v (тот же раунд, что и R_req, X ≤ R_req). Если SP_v > SP_e → X = (round(R_req) − 1) · N_slots + SP_v (предыдущий раунд, X <R_req). Занятие слота V в текущем раунде будет ссылаться на одноразовый номер после R_req ; его height_at будет наблюдаться после R_req и не сможет ограничить нижнюю границу H_skip. Отступив на один раунд назад, мы получим последний исполнитель V nonce ≤ R_req. Закрытая форма, используемая в псевдокоде: X = R_req − ((SP_e − SP_v) mod N_slots).
- Если SP_v ≤ SP_e → X = round(R_req) · N_slots + SP_v (тот же раунд, что и R_req, X ≤ R_req).
- Если SP_v > SP_e → X = (round(R_req) − 1) · N_slots + SP_v (предыдущий раунд, X <R_req). Занятие слота V в текущем раунде будет ссылаться на одноразовый номер после R_req ; его height_at будет наблюдаться после R_req и не сможет ограничить нижнюю границу H_skip. Отступив на один раунд назад, мы получим последний исполнитель V nonce ≤ R_req.
- Закрытая форма, используемая в псевдокоде: X = R_req − ((SP_e − SP_v) mod N_slots).
- Конечные точки интервала: h_X := height_at[X] — локальная высота V, когда он принял Diff[X] . По построению X ≤ R_req, поэтому h_X наблюдался не позднее самого запроса. h_carry := height_at[N_carry] = H(V) при приеме Diff[N_carry] . Инварианты (нормальность, а не режимы отказа): h_X ≤ h_carry (высоты монотонны при приеме) и h_carry ≤ H(V)_now (тривиально — V принимает N_carry прямо сейчас и штампует h_carry из H(V)_now ).
- h_X := height_at[X] — локальная высота V, когда он принял Diff[X] . По построению X ≤ R_req, поэтому h_X наблюдался не позднее самого запроса.
- h_carry := height_at[N_carry] = H(V) при приеме Diff[N_carry] .
- Инварианты (нормальность, а не режимы отказа): h_X ≤ h_carry (высоты монотонны при приеме) и h_carry ≤ H(V)_now (тривиально — V принимает N_carry прямо сейчас и штампует h_carry из H(V)_now ).
- **Diff[X] всегда доступен при приеме Diff[N_carry].** По построению X ≤ R_req и причинно-следственная связь (проверяемая в первую очередь на этом этапе) требует R_req ≤ N_carry, поэтому X ≤ N_carry. Поскольку Diff доступен только для добавления и принимается по порядку, каждый Diff[k] с k ≤ N_carry уже присутствует, когда V обрабатывает Diff[N_carry] .
- Краевой случай начальной загрузки. Единственная ситуация, в которой X не идентифицирует реальный предыдущий слот исполнителя V, - это round(R_req) = 0 ∧ SP_v > SP_e , где закрытая форма дает X < 0 — у V не было слота исполнителя до R_req в этом сеансе. Затем V возвращается к неявной привязке начала сеанса (наименьшему значению nonce, полученному V, обычно 0) в качестве нижней конечной точки h_X. Это только состояние холодного запуска; он не повторяется после того, как V выполнился хотя бы один раз.
- Результат этого шага: интервал высоты **I := [h_X, h_carry] **, использованный на шаге (4). Правильная граница находится в высоте основной сети, и каждый верификатор выводит ее из высот, которые он лично наблюдал ( h_X и h_carry ) — никаких предположений о высоте между хостами не требуется. Правило первого переноса. Если разработчик публикует несколько записей CarrySkip для одного и того же R_req, только самый ранний N_carry в Diff допускается в качестве входных данных для вынесения вердикта; более поздние дубликаты игнорируются (они все равно могут быть записаны для учета неправомерного поведения разработчиков, что выходит за рамки этого предиката). Это позволяет мне быть детерминированным среди верификаторов. Путь Б. Для MsgSkipProbe (случай C7) правило идентично: строго R_req := N_SP (зондовый одноразовый номер) и N_SP < N_carry. CarrySkip может обернуть либо CPoCskipResponse (отказ), либо CPoCProbeResponse (статус). Если переносимый результат готов, шаги (3–4) предиката Вердикт не применяются (нет отказа от оценки); вместо этого перенос записывается как квитанция планирования, используемая случаем C13. Работающие примеры (пусть N_slots = 4, слот V SP_v = v_slot = 2):
- Подтвердить-пропустить взаимное исключение (только путь A). Хост не может одновременно подтвердить и отклонить один и тот же вывод. Применяется только в том случае, если конверт CarrySkip имеет payload_kind =skip_response (Путь A); пропускается для пути B ( payload_kind =probe_response , который ссылается на N_SP и не имеет inference_id для коллизии). Процедура:
- Пусть inference_id* := Diff[R_req].inference_id — считывается из исходной записи MsgStartInference (которая уже должна быть в Diff по причинно-следственной связи, шаг 1).
- Сканируйте Diff на предмет любой записи, удовлетворяющей kind = MsgConfirmStart ∧ inference_id = inference_id* ∧ executor = H_i . Вызовите соответствующий одноразовый номер N_confirm, если он найден.
- Нет совпадений → перейдите к шагу (3).
- Сопоставление с N_confirm < N_carry → Недействительно для H_i, код_причины = double_claim_confirm_then_skip. Хост подтвердил вывод (и, следовательно, выполнил его или, должно быть, намеревался это сделать), а затем подписал противоречивый CPoCSkipResponse, который позже перенес D. Это криптографически доказуемая ложь: и MsgConfirmStart.executor_sig, и встроенная подпись CPoCskipResponse являются H_i.
- Соответствует N_confirm > N_carry (подтверждение появляется после переноса) → Недействительно для H_i, код_причины = double_claim_skip_then_confirm. Симметричное нарушение: хост отклонил запрос, а затем подтвердил и выполнил тот же вывод.
- Герметизация окна. Поскольку MsgConfirmStart для inference_id* может поступить после того, как V уже принял N_carry и вычислил вердикт, вердикт из шага (4) является предварительным для блоков основной сети W_seal после h_carry (W_seal параметризуется цепочкой — предложить ≈ 2 блока, соответствующие timeout_skip_gossip). Во время окна печати, если приземляется противоречивый MsgConfirmStart, V повторно запускает предикат и выдает заменяющий CPoCVote с ключом (N_carry, V_pubkey) (§ Консенсус/голосование); коллекционер хранит только самые последние. По истечении срока действия W_seal вердикт становится окончательным, и этот шаг перестает повторяться; любой MsgConfirmStart после печати — это проблема на уровне урегулирования, а не изменение вердикта cPoC. V отслеживает окно печати с помощью записи previsional_until[N_carry] = h_carry + W_seal, прикрепленной к pending_verdicts .
- Глубокая защита при поглощении (необязательно, но дешево). Уровень загрузки devshard ДОЛЖЕН отказаться добавлять (i) MsgConfirmStart для inference_id, если CarrySkip, встроенный ответ на пропуск которого ссылается на соответствующий R_req, уже существует в Diff, и (ii) CarrySkip(payload_kind =skip_response), ссылающийся на R_req, если MsgConfirmStart(inference_id = Diff[R_req].inference_id) уже существует существует. Поскольку порядок Diff уже детерминирован, это отклонение является чистой функцией Diff и не зависит от расы. Если это правило активно, сканирование выше фиксирует только гонку с уплотнением окна.
- Проверка ролей. H_i ∉ PoC_slot_set . В противном случае → Недействительно (хост имел POC_SLOT = true, пропускать нельзя).
- Запланируйте проверку на интервале I .
- ∃ H ∈ I : Schedule(H_i, H) ∈ {prepare, active} → кандидат действителен (с учетом (5)). Хост законно находился на cPoC на некоторой высоте, свидетелем которой V лично был в I; этого достаточно.
- ∀ H ∈ I : Schedule(H_i, H) == бездействие → кандидат недействителен (с учетом (5)). Хост заявляет об отказе от cPoC, но его нет в расписании ни на какой высоте в I.
- Высокая свежесть при приеме внутрь. Если конечные точки I ( h_X и h_carry ) строго подтверждены слоем синхронизации высоты (предположение 1), зафиксируйте кандидата из (4). Если уровень синхронизации по высоте помечает любую конечную точку как еще не строго подтвержденную, а вердикт расписания является состязательным (Invalid), V ДОЛЖЕН сохранять вердикт как неубедительный до тех пор, пока синхронизация по высоте не сообщит о подтверждении, охватывающем I, — затем повторно запустите шаг (4). Это может быть запланировано для будущих выпусков.
- Подпись/привязка. CPoCskipResponse должен быть правильно подписан H_i и ссылаться на R_req, как он отображается в Diff.
Выходные данные подаются в минимизацию Gossip (ниже) и для споров в FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md (./FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md).
Дела для обработки (дело/поток данных)
Легенда: R_req = nonce-запрос вывода пути A (или, в пути B, псевдоним зондового nonce N_SP ); N_carry = nonce, при котором CarrySkip добавляется к Diff; оба пути строго имеют R_req < N_carry. R обозначает раунд исполнителя размером N_slots.
C1 — Честный пропуск, честный разработчик (счастливый путь)
Настройка: Schedule(H_i, H(V)) = active, H_i ∉ PoC_slot_set, устройство ведет себя нормально.
Поток:
D → H_i : InferenceRequest(R_req) H_i → D : CPoCSkipResponse(R_req, active) D → H_{i+1} : следующий InferenceRequest в R_req+1, несущий пропущенный BLOB-объект (или отдельный CarrySkip при некотором N_carry ≥ R_req) V (= любой хост): on Diff[N_carry] → Verdict = Valid (окно nonce + расписание)
Ожидаемый вердикт: **Действительно**. Никаких сплетен, никаких триггеров финализации.
C2 — Вредоносный хост, ложный пропуск
Настройка: Schedule(H_i, H(V)) = IDLE, H_i ∉ PoC_slot_set, но H_i отвечает CPoCskipResponse, чтобы избежать работы.
Порядок действий: такой же, как C1, до момента, когда разработчик публикует CarrySkip. Каждый хост V тогда:
V on Diff[N_carry]: Compute Verdict(...) = Invalid # Проверка расписания не удалась, когда я испускаю CPoCVote(N_carry, verdict = Invalid, Signed_by=V) # p2p к D (и, возможно, сплетни) D собирает сообщения CPoCVote от разных хостов: if |votes(Invalid)| ≥ quorum_invalid: вердикт выносится, поскольку Invalid D передает пакет голосов на финализацию (сегодня) — ИЛИ — хосты публикуют голоса в следующем раунде финализации (будущий выпуск; см. § Консенсус/голосование)
Ожидаемый вердикт: недействительно (проверка расписания не удалась на интервале высот I). Недействительный результат не связан с завершением одной из сторон; это кворум CPoCVote от хостов, которые наблюдали Diff[N_carry] и независимо вынесли тот же вердикт. См. § Консенсус/голосование по протоколу сбора голосов и разделение «разработчик сегодня / самодоработка завтра».
C2' — мошенничество с двойной претензией (подтвердите и пропустите один и тот же запрос)
Настройка: H_i подписывает как MsgConfirmStart, так и CPoCSkipResponse для одного и того же inference_id (напрямую или через D, переносящего пропуск пропуска). Эти два сообщения криптографически несовместимы: MsgConfirmStart.executor_sig обязывает H_i выполнить вывод, а встроенный CPoCSkipResponse обязывает H_i отклонить его. Применимо только к пути A (payload_kind =skip_response); Путь B не имеет inference_id в переносе и не может инициировать этот случай.
Расход (подтвердите перед переносом):
Diff[R_req] : MsgStartInference(inference_id = I) Diff[N_confirm] : MsgConfirmStart(inference_id = I, executor = H_i) # H_i заявляет: "Я запустил это"... время проходит... Diff[N_carry] : встраивание CarrySkip CPoCSkipResponse(reference_nonce = R_req, подписано H_i) # противоречит подтверждению V при приеме Diff[N_carry]: предикат вердикта, шаг 2: inference_id* = Diff[R_req].inference_id = я сканирую Diff → нашел MsgConfirmStart(I, H_i) в N_confirm < N_carry ⇒ Недействительно для H_i (код_причины = double_claim_confirm_then_skip)
Поток (сначала пропустите, подтверждение поступает в окно уплотнения):
Diff[R_req] : MsgStartInference(inference_id = I) Diff[N_carry] : встраивание CarrySkip CPoCSkipResponse(…, подписано H_i) V при приеме: предварительно Действителен (или Недействителен по другим причинам); записывает предварительный_until = h_carry + W_seal в pending_verdicts... в окне печати... Diff[N_confirm] : MsgConfirmStart(I, H_i) # H_i утверждает вывод после отказа от него V при повторном запуске предиката: шаг 2 обнаруживает N_confirm > N_carry в пределах окна печати ⇒ Недействительно для H_i (код_причины = double_claim_skip_then_confirm) выдает заменяющее CPoCVote — сборщик заменяет предыдущий голос V за (N_carry, V_pubkey)
Поток (подтверждение приходит после окна уплотнения):
Diff[N_carry] : CarrySkip(...) # запечатан Действителен после W_seal Diff[N_confirm] : MsgConfirmStart(I, H_i) # слишком поздно, чтобы изменить вердикт cPoC V: НЕ повторно открывает вынесенный вердикт; вместо этого нарушение протокола передается на рассмотрение (завершение) как отдельное доказательство того, что Х_и подписал два противоречивых заявления о выводе I.
Ожидаемый вердикт: **Недействительно против H_i ** всякий раз, когда оба артефакта попадают в Diff в пределах окна печати друг друга. Урегулируется через стандартный кворум CPoCVote (§ Консенсус/голосование), при этом пакет голосов содержит код причины ∈ {double_claim_confirm_then_skip, double_claim_skip_then_confirm} и указатели на обе записи Diff в качестве криптографического доказательства противоречия. За пределами окна печати нарушение по-прежнему может быть разрезано, но на уровне расчета, а не в виде переворота предиката cPoC (сохраняет ограниченность окончательности вердикта).
Дополнительное усиление защиты от devshard-ingest. Осколок разработчика МОЖЕТ отказаться добавлять любое сообщение, если другое уже существует в Diff (предикат вердикта, шаг 2, «Эшелонированная защита при приеме»). В обычном случае это переносит отклонение с уровня предикатов на уровень шлюза; Шаг 2 предиката остается в силе в течение окна гонки, в течение которого оба сообщения могут законно одновременно поступить на уровень приема.
C3 — Поздний перенос разработчика (настоящий пропуск, опоздание)
Настройка: H_i вернул допустимый CPoCskipResponse в R_req во время своего окна cPoC (высота H_skip). Разработчик удерживает большой двоичный объект в течение произвольного количества раундов, а затем выдает CarrySkip по адресу N_carry ≫ R_req.
Поток:
D → devshard : MsgStartInference(R_req) # во время окна cPoC H_i H_i → D : CPoCSkipResponse(R_req, active) # p2p, подписанный H_i в H_skip ... время проходит; Дифференциал продвигается; Основная сеть обходит H_skip... D → devshard : CarrySkip(N_carry, CPoCSkipResponse) # поздний перенос V на Diff[N_carry]: SP_e = R_req mod N_slots; SP_v = v_slot X = R_req - ((SP_e - SP_v) mod N_slots) # тот же раунд, если SP_v ≤ SP_e, иначе предыдущий раунд h_X = height_at[X] (≈ H_skip — высота V, наблюдаемая в момент или до R_req) h_carry = H(V) при приеме Diff[N_carry] I = [H_skip, h_carry]; Schedule(H_i, H_skip) ∈ {prepare, active} ⇒ шаг 3 пройден Вердикт = Действительно
Ожидаемый вердикт: **Действительно**. Свидетельство хозяина правдиво для высоты I; опоздание не делает это задним числом ложью. Любой остаточный ущерб (запись вывода остается открытой, расчет застопорился) обрабатывается на уровне расчета ( MsgTimeoutInference{…CPOC} и сроки завершения), а не предикатом вердикта cPoC.
C3' — Ошибка причинно-следственной связи (поддельный перенос)
Настройка: разработчик публикует CarrySkip с N_carry < R_req (ссылается на запрос, который еще не вошел в Diff).
Поток: Шаг (1) предиката вердикта отклоняет конверт неравенства причинности R_req ≤ N_carry .
Ожидаемый вердикт: **Недействительно** в отношении оператора связи (подпись разработчика на CarrySkip), а не в отношении H_i. Это чистая проверка на подделку, не зависящая от какого-либо интервала высот.
C4 — POC_SLOT = истинный хост возвращает пропуск
Настройка: H_i ∈ PoC_slot_set (освобожден от вывода во время cPoC других), но отвечает CPoCskipResponse .
Поток: любой обычный запрос/ответ, приводящий к пропуску переноса.
Ожидаемый вердикт: недействительно (проверка роли не удалась). Вердикт выносится кворумом голосов (см. C2/§ Консенсус/голосование): каждый хост вычисляет один и тот же Invalid и генерирует CPoCVote ; собранный пакет — это доказательства, переданные на разрезание (H_i).
C5 — Пропустить окно подготовки
Настройка: Schedule(H_i, H(V)) = подготовить (зависит от политики).
Решение: Те же правила вердикта, что и для active .
C6 — Безрезультатно из-за неопределенности высоты.
Настройка: Schedule(H_i, H(V)) =dleid , но уровень синхронизации по высоте еще не строго подтвердил H(V) для nonce-окна (для нас это выходит за рамки — мы только потребляем его сигнал).
Поток: шаг вердикта (4) возвращает Inconclusive .
Ожидаемое действие: V еще не отправляет CPoCVote; он ждет подтверждения от слоя высоты. Если подтвержден Invalid , V выдает CPoCVote(Invalid), и стандартный поток голосования-кворума (§ Консенсус/голосование) собирает пакет. Если подтверждено Valid , голосование не проводится и никакие действия не предпринимаются.
C7 — Пропустить проверку (Путь B), результат отказа
Настройка: D хочет проверить статус cPoC от H_i без отправки запроса. Schedule(H_i, H) ∈ {active, подготовить} на высоте, на которой получен ответ на зонд.
Поток:
D → devshard : MsgSkipProbe(N_SP, target = H_i) # в Diff в N_SP H_i → D : CPoCProbeResponse(N_SP, result ∈ {cpoc_active, cpoc_prepare}) # p2p, подписанный H_i D → devshard : CarrySkip(N_carry, CPoCProbeResponse) # в Diff в N_carry > N_SP V on Diff[N_carry]: R_req := N_SP запускает предикат Verdict (шаги 1–5) без изменений
Ожидаемый вердикт: **Действительно** (тот же предикат, что и для пути A, применяется с R_req := N_SP ).
C7' — Пропустить зонд (Путь B), готовый результат
Настройка: D исследует H_i . H_i завершил свое окно cPoC и находится в состоянии READY_INFERENCE ( Schedule(H_i, H) = бездействие на высоте ответа).
Поток:
D → devshard : MsgSkipProbe(N_SP, target = H_i) H_i → D : CPoCProbeResponse(N_SP, результат = готов) # p2p, подписанный H_i D → devshard : CarrySkip(N_carry, CPoCProbeResponse) # в Diff при N_carry > N_SP V on Diff[N_carry]: обнаружить payload_kind = Probe_response AND result = готовая запись, планирование планирования: Ready_at[H_i] = (N_carry, h_carry) Шаги предиката вердикта (2–3) НЕ применяются (нет отказа в оценке)
Ожидаемый вердикт: неприменимо. Перенос — это квитанция планирования, а не подтверждение пропуска. Это обязывает разработчика возобновить маршрутизацию реального MsgStartInference в H_i при последующих одноразовых значениях H_i-slot. Постоянное отклонение после этого поступления вызывает срабатывание C13.
C8 — Нет ответа вообще (тайм-аут)
Настройка: H_i ничего не возвращает (ни вывод, ни пропуск).
Ожидаемые действия: выходят за рамки вердикта cPoC-skip. Управляется **USER_TIMEOUT ** в FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md (./FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md). Протокол cPoC в этом случае не выносит вердикта.
C9 — Сбор голосов с низкой нагрузкой (откровенные сплетни)
Настройка: после timeout_skip_gossip разница не продвинулась на один полный раунд, поэтому не каждый V обязательно видел перенос переноса, а сборщик голосов (см. § Консенсус/голосование) еще не достиг quorum_invalid .
Поток:
V1 отправляет SkipEvidenceGossip(Diff-refs) одноранговым узлам # отстающие одноранговые узлы догоняют одноранговые узлы Diff, восстанавливают Diff-refs, локально вычисляют Verdict и выдают CPoCVote, если их вердикт недействителен. Сборщик объединяет голоса (`D` для случаев сбоя хоста в этом выпуске; финальный раунд для голосов целевых разработчиков, когда существует самофинализация — см. § Консенсус/голосование)
Ожидаемый вердикт: независимо от того, что заявит кворум голосований на основе тех же доказательств различий. SkipEvidenceGossip предназначен только для облегчения доставки; он не вычисляет вердикт, он просто делает видимым тот же CarrySkip, чтобы отстающие участники могли голосовать.
C10 — Элисионный раунд с высокой нагрузкой
Настройка: Высокая частота запросов; разница естественным образом проходит мимо R_req + N_slots в течение timeout_skip_gossip.
Ожидаемое действие: эмиссия SkipEvidenceGossip не требуется; каждый V имеет доказательства по своей конструкции. Каждый V независимо вычисляет Verdict и, если он недействителен, выдает CPoCVote. Сборщик суммирует голоса как обычно.
C11 — Пакет доказательств спорного уровня
Настройка: вердикт недействителен (C2, C2', C4, C6-подтверждено-недействительно, C3' или C13).
Порядок действий: как только достигается quorum_invalid, сборщик собирает пакет доказательств, состоящий из: (i) ссылок на Diff для MsgStartInference/MsgSkipProbe, CarrySkip и (для C13) окна H_i -slot; (ii) набор сообщений CPoCVote, достигающих кворума; (iii) соответствующие входные данные расписания ( PoC_slot_set , Расписание на высотах в I ). Этот пакет передается FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md (./FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md) для включения в пакет финализации для основной сети — пакет является входными данными для слэшинга.
C12 — Рассинхронизация Исполнителя/расписания (ошибка верификатора)
Настройка: V имеет устаревший PoC_slot_set или неправильное расписание эпох (не представление большинства сети).
Ожидаемое поведение: Ви виноват в неправильном приговоре; это проблема оператора узла / обновления эпохи, а не ошибка хоста. Восстановление относится к уровню расписания/эпохи (выходит за рамки). Протокол должен регистрировать конфликт, чтобы операторы могли его обнаружить; он не должен наказывать H_i, если только выброс V не согласен.
C13 — разработчик приостанавливает работу с готового хоста (неправильное поведение маршрутизации)
Настройка: какой-то хост H_i подал сигнал о готовности (либо через CPoCProbeResponse(outcome = Ready), передаваемый в Diff в какой-то момент N_ready , либо потому, что Schedule(H_i, H) = бездействие в последних блоках основной сети W_ready, что строго подтверждает каждый верификатор). Тем не менее, разработчик не направляет реальный вывод в H_i :
- в одноразовых случаях, когда executor(n) = H_i (т.е. n mod N_slots = i), D продолжает отправлять MsgSkipProbe(target = H_i), а не MsgStartInference, или
- D вообще прекращает отправку сообщений в одноразовых номерах H_i-slot, продолжая отправлять сообщения в другие слоты.
Наблюдение (на каждом V). V подсчитывается в конечном окне раундов W_fair, заканчивающихся текущим nonce:
n_inf(H_i) = записи MsgStartInference с executor(n) = H_i ,
n_probe(H_i) = записи MsgSkipProbe, нацеленные на H_i ,
- готов ли H_i (для каждого получения Ready_at[H_i] или Schedule(H_i, H) = бездействия для каждого H ∈ [h_start_window, H(V)] ).
Предикат нарушения. Ready(H_i) ∧ n_probe(H_i) + n_miss(H_i) ≥ θ_fair ∧ n_inf(H_i) < θ_min_inf — т.е. через окно D отправил зонды или оставил H_i -слоты пустыми как минимум θ_fair раз, отправляя менее θ_min_inf реальных выводов в H_i, несмотря на то, что H_i готов. Точные значения (W_fair, θ_fair, θ_min_inf) параметризуются цепочкой (TBD; см. Открытые вопросы).
Поток:
1. Diff[N_ready] : перенос CarrySkip CPoCProbeResponse(outcome=ready) для H_i → каждая запись V Ready_at[H_i] = (N_ready, h_ready) 2. Одноразовые номера N_ready+1 … N_ready+W_fair·N_slots продвижение: V подсчитывает n_inf(H_i), n_probe(H_i) в одноразовых номерах H_i-слота из Diff 3. Предикат нарушения срабатывает в V: V переходит в состояние «удержания оповещения» для (D, H_i) 4. Нижестоящее исполнение: каждый V, который сам является будущим исполнителем для D, отказывается обслуживать запросы D (возвращает новый сигнал p2p `RouteFairnessRefusal(D, H_i, доказательства_refs)`) до тех пор, пока: (a) D не выдаст MsgStartInference(executor = H_i) И H_i не подтвердит это (MsgConfirmStart), ИЛИ (b) H_i повторно не войдет в cPoC (сигналы активный/подготовка через новый CPoCProbeResponse или через Schedule(H_i, H) с возвратом к {активному, подготовке}). 5. Когда (a) или (b) выполняются, V снимает предупреждение об удержании и возобновляет обслуживание D.
Ожидаемый вердикт: **Недействительно для разработчика**, а не для какого-либо хоста. Доказательство: квитанция Ready_at[H_i] + окно H_i -slot Diff, показывающее зонды/пустые слоты, но никаких запросов на вывод.
Почему принудительное исполнение осуществляется «следующими хостами». Единственный субъект, который может достоверно отказать D в дальнейшем обслуживании, — это хост, поставленный в очередь для выполнения следующего запроса D. Если эти хосты откажутся до тех пор, пока D не возобновит справедливую маршрутизацию, у D будет прямой экономический стимул прекратить удержание. По горячему пути не требуется двусторонний обмен данными по основной сети; решение является локальным для каждого V из одного и того же содержимого Diff, поэтому каждый честный хост получает одно и то же предупреждение.
Открытые параметры (отложены до открытых вопросов):
- W_fair, θ_fair, θ_min_inf пороги.
- Угасает ли квитанция Ready_at после повторного входа хоста в cPoC (предположительно да — как только Schedule(H_i, H) = активен снова, старые квитанции удаляются).
- Точный формат передачи RouteFairnessRefusal и то, попадет ли он также в Diff в качестве доказательства сокращения доли D.
C14 — Стратегическая задержка при низкой нагрузке (подавление пульса разработчика)
Применимость: возможно только при низкой нагрузке сеанса, в частности, когда Diff не содержит подписанных записей между R_req и N_carry, которые в противном случае сузили бы верхнюю границу V h_high на истинной высоте R_req. В любом сеансе с одновременным трафиком вывода промежуточные записи автоматически сужают полосу, и эта поверхность атаки закрывается сама собой.
Настройка. Schedule(H_i, h_req) = бездействие (хост не находится в cPoC в момент, когда R_req входит в Diff). Сразу после R_req трафик сеанса затихает: D не имеет других выводов для отправки. Затем злонамеренный H_i стратегически ожидает открытия своего следующего запланированного окна cPoC на некоторой высоте h > h_req , подписывает CPoCSkipResponse(R_req, active) во время этого более позднего окна и полагается на позднюю посадку CarrySkip D достаточно далеко в будущем, чтобы интервал высот V I = [h_X, h_carry] содержал h . В соответствии с семантикой ∃ H ∈ I (предикат вердикта, шаг 3) переносимый отказ теперь проходит, даже если хост простаивал в момент h_req и, следовательно, должен был сделать реальный вывод разработчику.
Поток (атака, без смягчения):
mainnet h_req : Diff[R_req] = MsgStartInference # H_i простаивает в h_req ... тихий сеанс; нет промежуточных записей Diff ... mainnet h+Δ : H_i входит в cPoC на высоте основной сети h > h_req H_i → D : CPoCSkipResponse(R_req, active) # подписано на высоте h (свежая ложь) mainnet h_carry: Diff[N_carry] = CarrySkip(встраивает выше) V при приеме: h_X ≈ h_req; h_carry ≫ h_req I = [h_X, h_carry] — широкая полоса, без промежуточной отметки ∃ H ∈ I : Schedule(H_i, H) = активно ⇒ шаг 3 проходит → Валидно (неверно)
Смягчение (сигнал разработчика). Когда у D есть невыполненный R_req и нет дальнейших выводов для отправки в текущем раунде ( R_req … R_req + N_slots ), D СЛЕДУЕТ выдавать легкий контрольный сигнал — MsgSkipProbe, нацеленный на естественного исполнителя следующего слота (R_req + 1) — в пределах ≈ 1 блока основной сети от R_req . Контрольный сигнал несет подписанную наблюдаемую_высоту ≈ h_req D, а ответный CPoCProbeResponse хоста (переносимый обратно через последующий CarrySkip) также несет подписанный наблюдаемый_высоту хоста. Оба штампа попадают в Diff по адресу nonce > R_req, обеспечивая жесткую верхнюю границу h_high для истинной высоты R_req.
Каденс — одно сердцебиение, один раунд, только во время простоя.
- Один выстрел в тихое окно. D излучает контрольный сигнал один раз в течение раунда R_req. Одной проштампованной записи достаточно, чтобы ужесточить h_high ; дополнительные сердцебиения не добавляют силы вердикту.
- Ограничено раундом R_req . Как только сеанс проходит мимо nonce R_req + N_slots (один полный раунд исполнителя), полоса для R_req уже ограничена сверху любой подписанной записью в этом окне. D НЕ ДОЛЖНО продолжать выдавать импульсы после закрытия раунда — последующие больше не улучшают вердикт для R_req.
- При условии отсутствия реального трафика. Heartbeats необходимы только в том случае, если в противном случае D оставил бы Diff в покое. Если D имеет реальный трафик MsgStartInference в очереди (любой одноразовый номер в [R_req + 1, R_req + N_slots] ), эти записи уже предоставляют h_high через свои собственные метки Observe_height — контрольное сообщение не генерируется.
Поток (уменьшенный):
mainnet h_req : Diff[R_req] = MsgStartInference(to H_i) # реальный запрос mainnet h_req+ε : Diff[R_req+1] = MsgSkipProbe(to H_{i+1}) # Heartbeat — если нет реальной последующей основной сети h_req+ε' : H_{i+1} → D : CPoCProbeResponse(N_SP=R_req+1, …) mainnet h_req+ε" : Diff[N_hb_carry] = CarrySkip(встраивает ответ зонда) # отметки наблюдаемой_высоты ≈ h_req ... (D перестает пульсировать; раунд закрывается) ... mainnet h_carry : Diff[N_carry] = CarrySkip(для настоящего R_req) V on прием Diff[N_carry]: h_X = height_at[X] (≈ h_req; нижняя граница) h_high = наблюдаемая_высота на самой ранней отметке в (≈ h_req+ε; пульс сжат) Diff[(R_req, N_carry)] Band = [h_X, h_high] — сворачивается до ≈ {h_req}. Шаг 3 теперь оценивается по полосе, близкой к точке: Schedule(H_i, h_req) = простой ⇒ Недействительно (атака закрыта)
Взаимодействие с другими делами.
- Если контрольный сигнал нацелен на сам H_i и H_i отвечает готово, ответ противоречит собственному более позднему CPoCskipResponse(R_req, active) — двойному утверждению, аналогичному MsgConfirmStart vs. Правило взаимного исключения CPoCskipResponse. Вердикт недействителен против H_i с первого взгляда, без необходимости разрешения группы.
- Если контрольный сигнал нацелен на хост следующего слота H_{i+1} (естественный случай, поскольку исполнителем R_req + 1 является H_{i+1} ), детектор удержания C13 ДОЛЖЕН исключать контрольные сигналы, излучаемые, пока R_req ожидает вердикт - зонд представляет собой механизм синхронизации по высоте, а не устойчивый шаблон маршрутизации. См. Открытые вопросы.
- Если D не может передать контрольный сигнал, несмотря на отсутствие альтернативного трафика, полоса остается широкой и атака «свежей ложью» успешна при ∃ H ∈ I . Таким образом, контрольное сообщение является обязательством стороны разработчика, а не обязательством, налагаемым протоколом с точки зрения хоста; небрежный или ленивый D подвергается опасности, что ему лгут. Это выравнивает стимулы: «биение сердца» защищает собственную оплату D за реальную работу.
Ожидаемый вердикт: при наличии контрольного сигнала тот же CPoCskipResponse, который стратегически прошел бы в широкой полосе, теперь не проходит шаг 3 и считается недействительным через стандартный кворум CPoCVote (§ Консенсус/голосование). Без тактового сигнала в сеансе с низкой нагрузкой точность вердикта протокола плавно ухудшается — вердиктом является то, что ∃ H ∈ I возвращает в широкой полосе — и штрафы на уровне расчетов за удержание хоста остаются единственным выходом.
Открытые параметры (отложены до открытых вопросов):
- Точное расстояние между R_req и тактовым сигналом (рекомендуется ≈ 1 блок основной сети; может быть меньше или меньше).
- Должен ли контрольный сигнал быть MsgSkipProbe или выделенным упрощенным сообщением без ожидания ответа. MsgSkipProbe используется здесь повторно, поскольку он уже содержит наблюдаемую высоту и использует существующие форматы проводов Diff, но вариант без ответа дешевле.
- Правило освобождения исключает долю удержания C13.
Консенсус/голосование
Каждый верификатор V вычисляет предикат Verdict (§ Поток данных) независимо от своего локального представления Diff и H(V) . Когда Verdict ∈ {Invalid, Inconclusive-pending-confirmation} (или срабатывает предупреждение разработчика об удержании C13), V подписывает и отправляет CPoCVote для этого N_carry . Для target = host(H_i) голоса адресуются D как сборщику (в этом выпуске). Для целевого именования D (C3', C13) доверенная агрегация здесь не указана — см. § Консенсус/голосование (оптимистический разрыв до самофинализации). Приговор принимается к окончательному вынесению только после того, как будет собран кворум независимых голосов; мнение отдельного проверяющего само по себе никого не ранит.
CPoCVote (новое p2p-сообщение, затем в пакет финализации)
Один CPoCVote стоит дешево; размер потока ограничен, поскольку только верификаторы с недействительным локальным вердиктом выдают один, и каждый из них является указателем на существующие записи Diff.
Коллекционер: этот релиз против самодоработки (включая голоса против D)
Случаи сбоя хоста (цель = хост(H_i) — C2, C2', C4, C6 и т. д.). Разработчик D является сборщиком голосов за этот выпуск:
- D уже владеет конвертом CarrySkip и знает, к какому N_carry относится голосование.
- D является экономически заинтересованной стороной, когда злонамеренный хост означает, что D не был обслужен.
Процедура сбора:
- Каждый V с недействительным вердиктом отправляет CPoCVote D через p2p (возможно, в сочетании с тем же каналом, который передает SkipEvidenceGossip).
- D объединяет отдельные подписи до тех пор, пока |votes(Invalid)| ≥ кворум_инвалид.
- D присоединяет пакет к финализации согласно FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md (./FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md) . Пакет голосов является входом для слэшинга.
Случаи с разработчиком-мишенью (целевые имена D — поддельный перенос C3’, удержание C13). D не может быть доверенным агрегатором голосов, который мог бы сократить или оспорить D. Нормативное намерение: после реализации самофинализации CPoCVote для этих целей ДОЛЖНЫ быть собраны и агрегированы в раунде финализации (тот же независимый от разработчика путь, что и для других расчетов), а не D .
Этот релиз — оптимистический разрыв. В протоколе не указан сборщик голосов, предназначенных для разработчиков. Мы предполагаем, что D ведет себя честно при передаче или агрегировании доказательств на практике, или что недействительные результаты C3'/C13 встречаются за пределами диапазона редко; Злонамеренная цензура D или удержание CPoCVote против самого себя — это известный раскрытый негативный случай, который планируется закрыть, когда произойдет самозавершение. Верификаторы по-прежнему выдают CPoCVote с target = Developer(D)/carrier(D), как указано; откладывается только доверенный путь агрегации.
Будущий выпуск (самодоработка). Когда финальный раунд объединяет CPoCVote без использования D:
- Каждый V по-прежнему излучает CPoCVote на стандартном канале; формат провода не изменился.
- В ходе финального раунда голоса собираются на детерминированной границе как для случаев сбоя хоста, так и для случаев сбоя разработчика, устраняя зависимость от D для любой цели.
- Это также удаляет режим сбоя «D прекращает отправку трафика и никогда не отправляет пакет голосов» в случаях сбоя хоста.
Кворум, взвешивание, тай-брейки
Точные значения — quorum_invalid (например, простое большинство или простое большинство). 2/3 с учетом доли), правила тай-брейка, взвешивание ставок и сопоставление голосов с суммами сокращения в основной сети — должны соответствовать уровню финализации/сокращения. Они параметризуются по цепочке и откладываются в FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md (./FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md) и спецификации сокращения основной сети. Этот документ гарантирует только:
- Каждый честный V получает один и тот же вердикт из одного и того же Diff + строго подтвержденного среза высоты (путем построения предиката Verdict).
- Нечестные голоса меньшинства не могут изменить правильный кворум, поскольку CPoCVote включает в себя Schedule_witness и его можно проверить во время завершения (нечестное голосование само по себе можно сократить).
Открытые вопросы (для формализации)
- **Происхождение PoC_slot_set:** устанавливается при инициализации условного депонирования (неизменяемый), а не запрашивается после инициализации и кэшируется. Различные режимы отказа.
- **Политика подготовки:** разрешен ли пропуск, пока Schedule = подготовить (считать активным) или запретить (считать бездействующим)? Флаг спецификации цепочкиskip_allowed_during_prepare.
- Подписание входных разделителей домена: cPoCRefusalContent (подпись хоста на CPoCSkipResponse, привязка inference_id + reference_nonce + причина), cPoCProbeResponseContent (подпись хоста на CPoCProbeResponse, привязка пробы_nonce + reference_nonce + результат), CarrySkipContent (подпись разработчика на CarrySkip, привязка N_carry + referenced_nonce + payload_kind + байтов хост_ответа) и ввод подписи для MsgSkipProbe (привязывает зонд_nonce = N_SP + target_host_id ).
- Макет Evidence-объекта для доработки (список Diff-refs, подписи, график-свидетели); совместно используется с FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md (./FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md).
- Пороговые значения C13 (W_fair, θ_fair, θ_min_inf) для предиката удержания разработчика: сколько одноразовых номеров H_i-слотов зондов/пустых слотов по сравнению с реальными выводами в течение скольких раундов квалифицируются как неправомерное поведение? Должен быть настроен так, чтобы законное краткое зондирование (например, одно подтверждение сразу после готовности перед возобновлением вывода) не вызывало оповещений.
- **ready_at lifecycle.** Когда именно истекает срок действия квитанции о готовности для H_i? Кандидаты: (а) по первому строго подтвержденному Расписанию(H_i, H) ∈ {active, подготовить} после поступления; (b) при любом последующем неготовом CPoCProbeResponse/CPoCskipResponse для H_i, переносимом в Diff; (c) жесткий TTL на высоте основной сети. Вероятно, все три с (a) ∨ (b) ∨ (c) .
- **Поверхность RouteFairnessRefusal.** Это просто сигнал отказа p2p между хостами, или он также должен попасть в Diff как подписанный артефакт, чтобы основная сеть могла сократить D ? В последнем случае он становится еще одним вариантом SubnetTx и нуждается в собственном домене подписи.
- Путь B без обратного пути через односторонний пропуск разработчика (будущая версия). Можно ли исключить двустороннюю передачу MsgSkipProbe → p2p-ответ → CarrySkip, разрешив D разместить маркер одностороннего пропуска со знаком D (например, MsgCPoCSkipMarker(nonce, target_host = H_i, базис = {N_prev_carry, h_prev})) в слоте H_i nonce и маршрутизация реального MsgStartInference в следующий слот? Требуется (i) проводной формат для маркера и его подписывающего домена; (ii) правило актуальности, привязанное к предыдущему CarrySkip для H_i — маркер действителен только до тех пор, пока предполагаемое расписанием окно cPoC, на которое ссылается N_prev_carry, не истекло на текущей высоте V; (iii) ограничение количества последовательных односторонних пропусков по каждому доказательству, поэтому один старый CarrySkip не может разрешать неограниченный пропуск; (iv) согласование с Ready_at[H_i] и детектором C13 — подтверждение готовности немедленно аннулирует выдающиеся полномочия маркера. Явно выходит за рамки текущей версии.
- Параметры кворума голосования. quorum_invalid (простое большинство против. 2/3 с учетом доли), независимо от того, подсчитываются ли голоса по каждому хосту или с учетом доли, правила тай-брейка и тайм-аут активности для сборщика, чтобы объявить «кворум не достигнут, считать действительным» параметризуются цепочкой и откладываются до спецификации финализации/сокращения.
- Сборщик самозавершения (будущий выпуск) — необходим для голосования, ориентированного на разработчиков. Когда раунд финализации объединяет CPoCVote, не полагаясь на D, нам нужны: (i) детерминированное граничное условие, которое запускает агрегацию голосов (высота блока, запечатывание сеанса и т. д.); (ii) явный прием CPoCVote с target = Developer(D) / Carrier(D) (C3', C13), поэтому агрегирование не остается на усмотрение D ; (iii) обработка голосов, прибывших с опозданием через границу; (iv) история миграции, поэтому старые узлы, которые все еще отправляют голоса по ошибке хоста в D, составляют с новым сборщиком. Сам формат передачи CPoCVote менять не нужно — только место назначения агрегации. Это закрывает оптимистический разрыв, описанный в § Консенсус/голосование (злонамеренная D-цензура голосует против себя). Явно выходит за рамки текущей версии.
- Политика пульса C14. (i) Точный интервал между R_req и контрольным сигналом (≈ 1 предложенный блок основной сети; настройка с учетом задержки в сети). (ii) Будет ли контрольный сигнал повторно использовать MsgSkipProbe или оправдывать выделенный облегченный вариант SubnetTx без ответов (который будет связывать только подписанную D наблюдаемую высоту и не потребует двустороннего обмена P2P). (iii) Правило исключения из подсчета удержания C13 для зондов, отправленных в то время, пока R_req ожидает вердикта, поэтому законное тактовое сообщение не считается удержанием от H_{i+1} . (iv) Являются ли поля Observe_height строго обязательными для MsgStartInference , MsgConfirmStart , MsgSkipProbe и CarrySkip для детерминированности проверяющего, или же на практике достаточно собственных отметок высоты_at[·] V — т. е. является ли замыкание C14 структурным в проводном формате или оперативно через контрольные сигналы поверх сегодняшних сообщений.
- C2' окно уплотнения W_seal . По умолчанию предлагается ≈ 2 блока основной сети (соответствует timeout_skip_gossip). Необходимо настроить с учетом (i) реалистичной задержки прибытия MsgConfirmStart после CarrySkip , (ii) того, как долго верификаторы могут разумно буферизовать записи pending_verdicts в предварительном состоянии, (iii) достаточно ли сильное сокращение уровня расчета для противоречий после подтверждения-затем-пропуска после печати, чтобы рассматривать закрытие печати как истинную границу. Если нет, рассмотрите возможность расширения W_seal или разрешения ограниченного количества переворотов после печати, записываемых как «позднее доказательство», а не как изменение приговора.
- Правило взаимного исключения Devshard-ingest (глубинная защита C2). Является ли отклонение MsgConfirmStart на уровне шлюза, когда CarrySkip(payload_kind =skip_response) для того же inference_id уже существует в Diff (и наоборот), ОБЯЗАТЕЛЬНО или СЛЕДУЕТ . MUST упрощает обоснование вердикта (сканирование на этапе 2 становится остаточной страховкой только для окна гонки), но создает более жесткую зависимость от того, что каждый конвейер приема ведет себя одинаково; СЛЕДУЕТ сохранить предикат как единственный источник истины, но оставить правило приема в качестве оппортунистической оптимизации. Тай-брейк также влияет на то, как реализации обрабатывают настоящую гонку, в которой оба сообщения действительны в свое время прибытия.
Связанные документы
- HEIGHT_SYNC_PROTOCOL_PROPOSAL.md (./HEIGHT_SYNC_PROTOCOL_PROPOSAL.md) — выходит за рамки этого документа; предоставляет H(V) в качестве оракула черного ящика.
- FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md (./FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md) — принимает недействительные вердикты, принимает решение о включении в пакеты финализации.

cPoC skip protocol (devshard) — proposal
Summary
In a devshard , hosts that run confirmation PoC (cPoC) must not serve normal inference for the duration of their PoC obligation. Other hosts must be able to tell whether a skip is legitimate (the skipping host is on the cPoC schedule at the relevant height) or abusive (lying, or refusing work). This document specifies the data flow and the cases that the cPoC protocol must handle.
Out of scope for this document:
- How each host obtains / agrees on mainnet height. That is solved by HEIGHT_SYNC_PROTOCOL_PROPOSAL.md (./HEIGHT_SYNC_PROTOCOL_PROPOSAL.md) (Omit / Anchor / Strong, deferred checks, etc.). Here we assume each host has a scalar **H(host) ** equal to the height known to the majority of validators / devshard hosts (its own follower + height-sync rules have converged on that value). Discrepancies at the level handled by the height-sync spec are that spec’s problem ; this document only distinguishes the cases where such a discrepancy affects a cPoC verdict and defers the discrepancy itself to height sync.
- Selection of POC_SLOT hosts (inference-exempt role) — policy/RNG, see Scope table.
- Mainnet settlement / slashing math — out of scope; this doc emits verdicts ( Valid / Invalid / Inconclusive ) and hands evidence to FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md (./FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md) .
Status: draft — data flow + cases specified below; wire schemas, chain hooks, and slashing predicates still TBD.
Scope
Shared assumptions (informative)
- Height oracle (provided by height sync, treated as black box here): Each host **V ** exposes a scalar **H(V)** — the mainnet height known to the majority of validators / devshard hosts as of **V ’s latest convergence with the height-sync layer. This doc does not re-specify how H(V) is computed, trusted, or refreshed; see HEIGHT_SYNC_PROTOCOL_PROPOSAL.md (./HEIGHT_SYNC_PROTOCOL_PROPOSAL.md) . When this doc says “height **H ” without qualification, read it as **H(V) at the moment V evaluates the case**.
- cPoC schedule: Given a host **H_i ** and a mainnet height **H** , there exists a deterministic predicate **Schedule(H_i, H) ∈ {idle, prepare, active}** derivable from chain / epoch state by anyone with that height. Semantics of prepare vs active are defined in the chain-side cPoC spec and out of scope here.
- **POC_SLOT roster:** For the epoch in force, a set **PoC_slot_set ** of hosts with POC_SLOT = true is available to every honest host (exact provenance — escrow init vs post-init query — is Open question 1 ).
- Executor schedule: Requests in a session are ordered by a monotonic nonce (linear increment). With **N_slots ** slots and fixed mapping **executor(nonce) = hosts[nonce mod N_slots]** , the same logical slot recurs at **nonce + N_slots** (one round ).
- Asynchronous developer traffic: The developer does not wait for a host response before sending the next request. A response to a request at **R_req ** is merged into the session's linearized diff at some later nonce **R_req + x **, **x ≥ 0** — not necessarily the same nonce. Any nonce-bound rule must work on the nonce at which a message appears in Diff , not on wall-clock pairing with the outbound request.
Notation (nonces used throughout)
All nonces below are monotonic indices into Diff (Data flow § Per-session local state). They are defined here once so later sections can reference them without re-introducing each.
Problem statement
1. Skip correctness
A host that returns “skipping because of cPoC” may be:
Honest — Schedule(H_i, H) ∈ {prepare, active} and H_i ∉ PoC_slot_set , or
- Malicious — returning CPoC_SKIP while not scheduled / while in PoC_slot_set (avoids work).
The protocol must let every honest verifier **V ** reach the same verdict from the same diff , using **H(V) ** as the height oracle (assumption 1 ).
2. Developer replay / withholding
A developer could hold a host's cPoC skip response and later attach it via CarrySkip . Mitigation is layered:
- At the cPoC verdict layer (this doc): freshness is bounded in mainnet heights , not rounds, via the interval I = [h_X, h_carry] each verifier personally witnesses (§ Nonce binding). A late carry of a genuine skip blob remains Valid — it was truthful at a height in I ; a late reveal does not retroactively make it a lie. Only skips that were never legitimate at any height in I produce Invalid .
- At the settlement layer (out of scope here): the remaining harm from late carries — inference records kept open, stale evidence used to stall settlement — is handled by MsgTimeoutInference{…CPOC} timeouts and finalization deadlines.
3. Gossip volume
Under high inference rate, if most hosts skip during cPoC, per-skip gossip is unacceptable:
- No gossip inside a normal round if diffs already propagate the evidence.
- Dispute-grade evidence rides on finalization / state sharing (./FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md) rather than a parallel flood channel.
Design principles (high level)
The formalization in § Data flow and the cases in § Cases to handle are chosen to satisfy the following principles. Nonce symbols ( R_req , N_SP , N_carry , X , N_slots ) are defined in Shared assumptions → Notation ; H(V) , Schedule , and PoC_slot_set in Shared assumptions items 1–3; timeout_skip_gossip under § Gossip minimization.
Two request paths
The developer chooses one of two shapes when opening a request; both converge on the same Verdict predicate.
Path A — inference with possible cPoC refusal (full payload). Developer submits a real inference request; the host either confirms and runs it, or refuses because of cPoC.
D → devshard : MsgStartInference(R_req, prompt_hash, …) [into Diff at R_req] happy path → H_i → devshard : MsgConfirmStart(R_req) [into Diff] → … → MsgFinishInference cPoC path → H_i → D : CPoCSkipResponse(R_req, reason) [p2p; NOT in Diff] D → devshard : CarrySkip(N_carry, <embedded CPoCSkipResponse>) [into Diff at N_carry]
Path B — lightweight skip probe (no prompt, no inference cost). Developer asks H_i to report its cPoC state without paying a prompt. The host does not execute inference ; it just returns a signed status. The response has two possible outcomes :
- Refusal — H_i is still on cPoC ( cpoc_active or cpoc_prepare ); behaves like a Path-A skip for verdict purposes.
- Ready — H_i has finished cPoC and is READY_INFERENCE ; the developer should resume sending real MsgStartInference to H_i .
D → devshard : MsgSkipProbe(N_SP) [into Diff at N_SP] H_i → D : CPoCProbeResponse(N_SP, outcome ∈ {cpoc_active, cpoc_prepare, ready}) [p2p; NOT in Diff] D → devshard : CarrySkip(N_carry, <embedded CPoCProbeResponse>) [into Diff at N_carry] # N_SP < N_carry strictly (two distinct Diff entries)
A ready outcome carried into Diff is not an Invalid skip — the verdict predicate simply does not apply (no refusal to validate). It is instead a scheduling receipt : V records that H_i signalled ready at a height in [h_X, h_carry] . Subsequent developer behaviour is checked against that receipt by C13 (developer keeps probing / skipping a ready host — see § Cases).
Future optimization (deferred, see Open question §8). Once D has a fresh CarrySkip proving H_i is on cPoC, subsequent skips of H_i within the same cPoC window should not need a full probe roundtrip: D can place a single developer-signed marker into Diff at H_i 's slot and route the real request to H_{i+1} . Collapses the three-message Path-B triple ( MsgSkipProbe → CPoCProbeResponse → CarrySkip ) to one D-signed entry per repeated skip. Out of scope for this release ; the current doc specifies only the explicit-probe flow.
Key invariants shared by both paths:
- Host responses are p2p and not directly observable by verifiers. Whether CPoCSkipResponse (Path A refusal) or CPoCProbeResponse (Path B status), the host's signed statement only enters the verifier's field of view when the developer echoes it via **CarrySkip ** into Diff .
- **CarrySkip is the primary verdict-bearing artifact in Diff .** Every V computes Verdict from Diff[N_carry] . For Path A , the predicate additionally scans Diff for a MsgConfirmStart matching the same inference_id ; if one exists (in either direction relative to N_carry ), the verdict is Invalid against H_i for double-claim (see § Verdict predicate, step 2, and § Cases → C2').
- Two distinct Diff entries per path. Both paths have R_req < N_carry (resp. N_SP < N_carry ) strictly: different proto messages occupy different nonces, and the developer signature on CarrySkip binds bytes that only exist after the host's p2p response arrives.
- Settlement is decoupled. Once CarrySkip has reached a final Verdict , closing the inference record at chain level uses the existing MsgTimeoutInference{reason = TIMEOUT_REASON_CPOC} path (new enum value); this settlement step is not what the verdict depends on.
Everything below — nonce binding, gossip minimization, data flow, cases — applies to both paths uniformly; the only Path-B specialization is the additional ready outcome (and its follow-on case C13).
Nonce binding (height-interval freshness)
Because of asynchronous developer traffic (Shared assumptions, item 5 ), the response to a request sent at nonce **R_req ** may appear in Diff only at nonce **R_req + x** , **x ≥ 0** . The delay x is not bounded in rounds — rounds can be far faster than mainnet blocks or host response, so many rounds may legitimately elapse between R_req and N_carry . Verdicts therefore bind to a height interval that each verifier constructs locally from its own observations of Diff :
- Reference nonce of a skip attestation = **R_req ** (the request it answers), stated inside the signed CPoCSkipResponse . (Term chosen to avoid collision with the height-sync "Anchor", which is out of scope here.)
- Carry nonce = **N_carry ** (the nonce at which CarrySkip is appended to Diff and becomes visible to verifiers).
- Witness nonce X = the latest nonce ≤ R_req whose executor is V itself — a nonce V personally handled, so height_at[X] is a height V actually observed no later than R_req . The exact formula (same round vs. previous round, depending on SP_v vs. SP_e ) and its derivation are given in § Verdict predicate, step 1.
- Height interval I = [h_X, h_carry] where h_X := height_at[X] and h_carry := height_at[N_carry] = H(V) at ingest of Diff[N_carry] . This interval bounds the set of mainnet heights at which the host's skip could physically have been produced, as seen through this verifier's local clock.
- Legitimacy test (anti-cheat, not anti-replay). The skip is legitimate iff ∃ H ∈ I : Schedule(H_i, H) ∈ {prepare, active} . If no height in I places H_i on the cPoC schedule, the skip could not have been truthful at any moment V witnessed → Invalid against H_i . A stale but genuine skip blob replayed well after the host returned to READY_INFERENCE is still Valid — the host was legitimately refusing at some height in I ; a late carry does not retroactively make it a lie. (Replay / withholding harms settlement, not the cPoC verdict — see § Consensus / voting and the settlement-only row in the primitives table.)
- Height attribution is local only. Each verifier computes h_X and h_carry from its own height_at[·] map; the developer's or host's claimed height in CPoCSkipResponse is informational and is not input to the verdict.
Gossip minimization
- Round-based elision (high load): If within timeout_skip_gossip after N_carry the session advances to N_carry + N_slots (one full round), every honest verifier has seen the evidence via the diff. No dedicated gossip is emitted.
- Timeout-based gossip (low load): Otherwise, any V with a non- Valid verdict MAY emit a compact SkipEvidenceGossip pointing into Diff . Peers re-run the verdict predicate locally.
- Finalization alignment: Global, dispute-grade evidence rides with FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md (./FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md) rather than a parallel flood channel.
Parameter **timeout_skip_gossip ** (proposal: ≈ 2 mainnet blocks) is chain-parametrized ; its exact value is out of scope here.
Data flow (formalized)
Parties
Per-session local state (at each **V** )
Primitives
Names in subnet/proto/subnet/v1/{tx,diff}.proto unless marked (new) . The verdict-predicate input set is MsgStartInference , MsgConfirmStart , MsgSkipProbe , and CarrySkip ; the verdict-settlement input set is CPoCVote ; the remaining messages are p2p carriers ( CPoCSkipResponse , CPoCProbeResponse ), delivery gossip ( SkipEvidenceGossip ), or final settlement ( MsgTimeoutInference{…CPOC} ).
End-to-end flow (happy path, host actually on cPoC)
nonce R_req nonce R_req+1..N_carry-1 D ─────────────── InferenceRequest(R_req) ─────────────▶ H_i (other requests to H_{i+1..}) │ H_i in cPoC │ D ◀─────────── CPoCSkipResponse(R_req, reason) ───────────┘ (arrives async, R_req + x in Diff) D ───── CarrySkip(N_carry) embeds CPoCSkipResponse(R_req, …) ──▶ any host ──▶ Diff[N_carry] each V observing L: on ingest Diff[N_carry]: record height_at[N_carry] = H(V) evaluate Verdict(…) using H(V) and nonce-window rules below
Verdict predicate (normative shape)
V computes **Verdict(skip_evidence) ∈ {Valid, Invalid, Inconclusive}** as:
- Causality and height-interval construction. Applied to the first CarrySkip in Diff that references R_req (see "First-carry rule" below):
- Causality: R_req ≤ N_carry . A CarrySkip cannot reference a request that has not yet entered Diff . Failure → Invalid against the carrier (developer signature on CarrySkip ), not against H_i .
- Witness nonce X (per § Design principles → Nonce binding). Let SP_e = R_req mod N_slots , SP_v = v_slot , round(R_req) = ⌊R_req / N_slots⌋ . Then: If SP_v ≤ SP_e → X = round(R_req) · N_slots + SP_v (same round as R_req , X ≤ R_req ). If SP_v > SP_e → X = (round(R_req) − 1) · N_slots + SP_v ( previous round, X < R_req ). Taking V's slot in the current round would reference a nonce after R_req ; its height_at would be observed after R_req and could not lower-bound H_skip . Stepping back one round gives the latest executor-of- V nonce ≤ R_req . Closed form used in pseudocode: X = R_req − ((SP_e − SP_v) mod N_slots) .
- If SP_v ≤ SP_e → X = round(R_req) · N_slots + SP_v (same round as R_req , X ≤ R_req ).
- If SP_v > SP_e → X = (round(R_req) − 1) · N_slots + SP_v ( previous round, X < R_req ). Taking V's slot in the current round would reference a nonce after R_req ; its height_at would be observed after R_req and could not lower-bound H_skip . Stepping back one round gives the latest executor-of- V nonce ≤ R_req .
- Closed form used in pseudocode: X = R_req − ((SP_e − SP_v) mod N_slots) .
- Interval endpoints: h_X := height_at[X] — V's local height when it ingested Diff[X] . By construction X ≤ R_req , so h_X was observed no later than the request itself. h_carry := height_at[N_carry] = H(V) at ingest of Diff[N_carry] . Invariants (sanity, not failure modes): h_X ≤ h_carry (heights are monotonic at ingest), and h_carry ≤ H(V)_now (trivially — V is ingesting N_carry right now and stamps h_carry from H(V)_now ).
- h_X := height_at[X] — V's local height when it ingested Diff[X] . By construction X ≤ R_req , so h_X was observed no later than the request itself.
- h_carry := height_at[N_carry] = H(V) at ingest of Diff[N_carry] .
- Invariants (sanity, not failure modes): h_X ≤ h_carry (heights are monotonic at ingest), and h_carry ≤ H(V)_now (trivially — V is ingesting N_carry right now and stamps h_carry from H(V)_now ).
- **Diff[X] is always available when Diff[N_carry] is being ingested.** By construction X ≤ R_req , and causality (checked first in this step) requires R_req ≤ N_carry , so X ≤ N_carry . Because Diff is append-only and ingested in order, every Diff[k] with k ≤ N_carry is already present when V processes Diff[N_carry] .
- Bootstrap edge case. The only situation in which X does not identify a real prior executor-slot of V is round(R_req) = 0 ∧ SP_v > SP_e , where the closed form yields X < 0 — V had no executor slot before R_req in this session. V then falls back to the implicit session-start anchor (the lowest nonce V has ingested, typically 0) as the lower endpoint h_X . This is a cold-start condition only; it does not recur once V has executed at least once.
- Output of this step: the height interval **I := [h_X, h_carry] **, consumed by step (4). The correct bound is in mainnet heights, and each verifier derives it from heights it personally observed ( h_X and h_carry ) — no cross-host height assumption required. First-carry rule. If the developer publishes multiple CarrySkip entries for the same R_req , only the earliest N_carry in Diff is admitted as input to the verdict; later duplicates are ignored (they may still be recorded for developer-misbehavior accounting, out of scope for this predicate). This keeps I deterministic across verifiers. Path B. For MsgSkipProbe (case C7 ) the rule is identical, with R_req := N_SP (the probe nonce) and N_SP < N_carry strictly. CarrySkip may wrap either a CPoCSkipResponse (refusal) or a CPoCProbeResponse (status). If the carried outcome is ready , steps (3–4) of the Verdict predicate do not apply (no refusal to evaluate); the carry is instead recorded as a scheduling receipt consumed by case C13 . Worked examples (let N_slots = 4 , V 's slot SP_v = v_slot = 2 ):
- Confirm-Skip mutual exclusion (Path A only). The host cannot both confirm and refuse the same inference. Applied only when the CarrySkip envelope has payload_kind = skip_response (Path A); skipped for Path B ( payload_kind = probe_response , which references N_SP and has no inference_id to collide). Procedure:
- Let inference_id* := Diff[R_req].inference_id — read from the original MsgStartInference entry (which must already be in Diff by causality, step 1).
- Scan Diff for any entry satisfying kind = MsgConfirmStart ∧ inference_id = inference_id* ∧ executor = H_i . Call the matching nonce N_confirm if found.
- No match → proceed to step (3).
- Match with N_confirm < N_carry → Invalid against H_i , reason_code = double_claim_confirm_then_skip . The host confirmed the inference (and therefore ran it, or must have intended to) and then signed a contradictory CPoCSkipResponse that D later carried. This is a cryptographically provable lie: both the MsgConfirmStart.executor_sig and the embedded CPoCSkipResponse signature are H_i 's.
- Match with N_confirm > N_carry (confirm appears after the carry) → Invalid against H_i , reason_code = double_claim_skip_then_confirm . Symmetric violation: the host refused the request, then later confirmed and ran the same inference.
- Sealing window. Because MsgConfirmStart for inference_id* may arrive after V has already ingested N_carry and computed a verdict, the verdict from step (4) is provisional for W_seal mainnet blocks after h_carry ( W_seal is chain-parametrized — propose ≈ 2 blocks, matching timeout_skip_gossip ). During the seal window, if a contradictory MsgConfirmStart lands, V re-runs the predicate and emits a superseding CPoCVote keyed on (N_carry, V_pubkey) (§ Consensus / voting); the collector keeps only the latest. After W_seal expires the verdict is final and this step stops re-firing; any post-seal MsgConfirmStart is a settlement-layer issue, not a cPoC verdict flip. V tracks the seal window via a provisional_until[N_carry] = h_carry + W_seal entry attached to pending_verdicts .
- Defence in depth at ingest (optional but cheap). The devshard ingest layer SHOULD refuse to append (i) a MsgConfirmStart for inference_id if a CarrySkip whose embedded skip-response references the corresponding R_req already exists in Diff , and (ii) a CarrySkip(payload_kind = skip_response) referencing R_req if MsgConfirmStart(inference_id = Diff[R_req].inference_id) already exists. Because Diff ordering is already deterministic, this rejection is a pure function of Diff and race-free. With this rule active the scan above catches only the seal-window race.
- Role check. H_i ∉ PoC_slot_set . Otherwise → Invalid (host had POC_SLOT = true , must not skip).
- Schedule check over interval I .
- ∃ H ∈ I : Schedule(H_i, H) ∈ {prepare, active} → candidate Valid (subject to (5)). The host was legitimately on cPoC at some height V personally witnessed in I ; that is sufficient.
- ∀ H ∈ I : Schedule(H_i, H) == idle → candidate Invalid (subject to (5)). The host claims cPoC refusal but is not on the schedule at any height in I .
- Height freshness at ingest. If the endpoints of I ( h_X and h_carry ) are strictly confirmed by the height-sync layer (assumption 1 ), commit to the candidate from (4). If the height-sync layer flags either endpoint as not yet strictly confirmed , and the schedule verdict is adversarial ( Invalid ), V MUST hold the verdict as Inconclusive until height sync reports confirmation covering I — then re-run step (4). This could be scheduled for future releases
- Signature / binding. CPoCSkipResponse must be validly signed by H_i and reference R_req as it appears in Diff .
Outputs feed Gossip minimization (below) and, for disputes, FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md (./FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md) .
Cases to handle (case / dataflow)
Legend: R_req = Path-A inference-request nonce (or, in Path B, aliased to the probe nonce N_SP ); N_carry = nonce at which CarrySkip is appended to Diff ; both paths have R_req < N_carry strictly. R denotes the executor round of size N_slots .
C1 — Honest skip, honest developer (happy path)
Setup: Schedule(H_i, H(V)) = active , H_i ∉ PoC_slot_set , dev behaves normally.
Flow:
D → H_i : InferenceRequest(R_req) H_i → D : CPoCSkipResponse(R_req, active) D → H_{i+1} : next InferenceRequest at R_req+1 carrying skip blob (or separate CarrySkip at some N_carry ≥ R_req) V (= any host): on Diff[N_carry] → Verdict = Valid (nonce window + schedule)
Expected verdict: **Valid **. No gossip, no finalization trigger.
C2 — Malicious host, fake skip
Setup: Schedule(H_i, H(V)) = idle , H_i ∉ PoC_slot_set , but H_i replies CPoCSkipResponse to avoid work.
Flow: Same as C1 up to the point the developer publishes CarrySkip . Each host V then:
V on Diff[N_carry]: compute Verdict(...) = Invalid # Schedule check fails at I emit CPoCVote(N_carry, verdict = Invalid, signed_by=V) # p2p to D (and optionally gossip) D collects CPoCVote messages from distinct hosts: if |votes(Invalid)| ≥ quorum_invalid: verdict is settled as Invalid D hands the vote bundle to finalization (today) — OR — hosts publish votes at the next finalization round (future release; see § Consensus / voting)
Expected verdict: Invalid (Schedule check fails on the height interval I ). The Invalid outcome is not attached to finalization by one party; it is the quorum of CPoCVote s from hosts that observed Diff[N_carry] and independently reached the same verdict. See § Consensus / voting for the vote-collection protocol and the "developer today / self-finalization tomorrow" split.
C2' — Double-claim fraud (confirm and skip the same request)
Setup: H_i signs both a MsgConfirmStart and a CPoCSkipResponse for the same inference_id (directly or via D carrying the skip blob). The two messages are cryptographically incompatible: MsgConfirmStart.executor_sig commits H_i to running the inference, and the embedded CPoCSkipResponse commits H_i to refusing it. Applicable only to Path A ( payload_kind = skip_response ); Path B has no inference_id on the carry and cannot trigger this case.
Flow (confirm before carry):
Diff[R_req] : MsgStartInference(inference_id = I) Diff[N_confirm] : MsgConfirmStart(inference_id = I, executor = H_i) # H_i claims "I ran it" ... time passes ... Diff[N_carry] : CarrySkip embedding CPoCSkipResponse(reference_nonce = R_req, signed by H_i) # contradicts confirm V on ingest of Diff[N_carry]: Verdict predicate, step 2: inference_id* = Diff[R_req].inference_id = I scan Diff → found MsgConfirmStart(I, H_i) at N_confirm < N_carry ⇒ Invalid against H_i (reason_code = double_claim_confirm_then_skip) emit CPoCVote(Invalid, target = host(H_i), reason_code = …)
Flow (skip carried first, confirm arrives inside the seal window):
Diff[R_req] : MsgStartInference(inference_id = I) Diff[N_carry] : CarrySkip embedding CPoCSkipResponse(…, signed by H_i) V on ingest: provisional Valid (or Invalid on other grounds); records provisional_until = h_carry + W_seal in pending_verdicts ... within the seal window ... Diff[N_confirm] : MsgConfirmStart(I, H_i) # H_i claims the inference after refusing it V on re-run of the predicate: step 2 detects N_confirm > N_carry within seal window ⇒ Invalid against H_i (reason_code = double_claim_skip_then_confirm) emit superseding CPoCVote — collector replaces V's prior vote for (N_carry, V_pubkey)
Flow (confirm arrives after the seal window):
Diff[N_carry] : CarrySkip(...) # sealed Valid after W_seal Diff[N_confirm] : MsgConfirmStart(I, H_i) # too late to flip the cPoC verdict V: does NOT re-open the settled verdict; the protocol violation is instead handed off to settlement (finalization) as stand-alone evidence that H_i signed two contradictory statements about inference I.
Expected verdict: **Invalid against H_i ** whenever both artifacts land in Diff within the seal window of each other. Settled via the standard CPoCVote quorum (§ Consensus / voting), with the vote bundle carrying reason_code ∈ {double_claim_confirm_then_skip, double_claim_skip_then_confirm} and pointers to both Diff entries as the cryptographic evidence of the contradiction. Outside the seal window the violation is still slashable, but at the settlement layer rather than as a cPoC-predicate flip (keeps verdict finality bounded).
Optional devshard-ingest hardening. The devshard MAY refuse to append either message when the other already exists in Diff (Verdict predicate, step 2, "Defence in depth at ingest"). This shifts the rejection from the predicate layer to the gateway layer for the common case; the predicate's step 2 remains in force for the race window during which both messages can legitimately arrive at the ingest layer concurrently.
C3 — Developer late carry (genuine skip, late)
Setup: H_i returned a legitimate CPoCSkipResponse at R_req during its cPoC window (height H_skip ). Developer holds the blob for arbitrarily many rounds and later emits CarrySkip at N_carry ≫ R_req .
Flow:
D → devshard : MsgStartInference(R_req) # during H_i's cPoC window H_i → D : CPoCSkipResponse(R_req, active) # p2p, signed by H_i at H_skip ... time passes; Diff advances; mainnet advances past H_skip ... D → devshard : CarrySkip(N_carry, CPoCSkipResponse) # late carry V on Diff[N_carry]: SP_e = R_req mod N_slots; SP_v = v_slot X = R_req − ((SP_e − SP_v) mod N_slots) # same round if SP_v ≤ SP_e, else previous round h_X = height_at[X] (≈ H_skip — V's height observed at or before R_req) h_carry = H(V) at ingest of Diff[N_carry] I = [H_skip, h_carry]; Schedule(H_i, H_skip) ∈ {prepare, active} ⇒ step 3 passes Verdict = Valid
Expected verdict: **Valid **. The host's attestation is truthful for a height in I ; lateness does not retroactively make it a lie. Any residual harm (inference record kept open, stalled settlement) is handled at the settlement layer ( MsgTimeoutInference{…CPOC} and finalization deadlines), not by the cPoC verdict predicate.
C3' — Causality failure (forged carry)
Setup: Developer publishes a CarrySkip with N_carry < R_req (references a request that has not yet entered Diff ).
Flow: Step (1) of the verdict predicate rejects the envelope on the causality inequality R_req ≤ N_carry .
Expected verdict: **Invalid ** against the carrier (developer signature on CarrySkip ), not against H_i . This is a pure forgery check, independent of any height interval.
C4 — POC_SLOT = true host returns skip
Setup: H_i ∈ PoC_slot_set (inference-exempt during others’ cPoC), yet replies CPoCSkipResponse .
Flow: any normal request/response leading to a carried skip.
Expected verdict: Invalid (Role check fails). Verdict is settled by vote quorum (see C2 / § Consensus / voting): every host computes the same Invalid and emits CPoCVote ; the collected bundle is the evidence handed to slashing ( H_i ).
C5 — Skip during prepare window
Setup: Schedule(H_i, H(V)) = prepare (policy-dependent).
Decision: Same verdict rules as active .
C6 — Inconclusive due to height uncertainty
Setup: Schedule(H_i, H(V)) = idle , but height-sync layer has not yet strictly confirmed H(V) for the nonce-window (out of scope for us — we only consume its signal).
Flow: Verdict step (4) returns Inconclusive .
Expected action: V does not emit a CPoCVote yet; it waits for the height layer to confirm. If confirmed Invalid , V emits CPoCVote(Invalid) and the standard vote-quorum flow (§ Consensus / voting) collects the bundle. If confirmed Valid , no vote is emitted and no action is taken.
C7 — Skip probe (Path B), refusal outcome
Setup: D wants a cPoC status check from H_i without submitting a prompt. Schedule(H_i, H) ∈ {active, prepare} at the height the probe is answered.
Flow:
D → devshard : MsgSkipProbe(N_SP, target = H_i) # into Diff at N_SP H_i → D : CPoCProbeResponse(N_SP, outcome ∈ {cpoc_active, cpoc_prepare}) # p2p, signed by H_i D → devshard : CarrySkip(N_carry, CPoCProbeResponse) # into Diff at N_carry > N_SP V on Diff[N_carry]: R_req := N_SP run the Verdict predicate (steps 1–5) unchanged
Expected verdict: **Valid ** (same predicate as Path A, applied with R_req := N_SP ).
C7' — Skip probe (Path B), ready outcome
Setup: D probes H_i . H_i has finished its cPoC window and is in READY_INFERENCE ( Schedule(H_i, H) = idle at the answering height).
Flow:
D → devshard : MsgSkipProbe(N_SP, target = H_i) H_i → D : CPoCProbeResponse(N_SP, outcome = ready) # p2p, signed by H_i D → devshard : CarrySkip(N_carry, CPoCProbeResponse) # into Diff at N_carry > N_SP V on Diff[N_carry]: detect payload_kind = probe_response AND outcome = ready record scheduling receipt: ready_at[H_i] = (N_carry, h_carry) Verdict predicate steps (2–3) do NOT apply (no refusal to evaluate)
Expected verdict: not applicable. The carry is a scheduling receipt , not a skip attestation. It obliges the developer to resume routing real MsgStartInference to H_i at subsequent H_i -slot nonces. Persistent deviation after this receipt triggers C13 .
C8 — No response at all (timeout)
Setup: H_i returns nothing (neither inference nor skip).
Expected action: Out of scope of cPoC-skip verdict. Governed by **USER_TIMEOUT ** in FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md (./FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md) . cPoC protocol contributes no verdict in this case.
C9 — Low-load vote collection (explicit gossip)
Setup: After timeout_skip_gossip the diff has not advanced one full round, so not every V has necessarily seen the carried skip and the vote collector (see § Consensus / voting) has not yet reached quorum_invalid .
Flow:
V1 emits SkipEvidenceGossip(Diff-refs) to peers # lagging peers catch up on Diff peers reconstruct Diff-refs, compute Verdict locally, and emit CPoCVote if their verdict is non-Valid collector aggregates votes (`D` for host-fault cases this release; finalization round for developer-target votes when self-finalization exists — see § Consensus / voting)
Expected verdict: whatever the vote quorum declares on the same Diff evidence. SkipEvidenceGossip is a delivery aid only; it does not compute a verdict, it just makes the same CarrySkip visible so lagging peers can vote.
C10 — High-load round elision
Setup: High request rate; the diff naturally advances past R_req + N_slots within timeout_skip_gossip .
Expected action: No SkipEvidenceGossip emission needed; every V has the evidence by construction. Each V independently computes Verdict and, if non- Valid , emits CPoCVote . The collector aggregates votes as usual.
C11 — Dispute-grade evidence bundle
Setup: A verdict is Invalid (C2, C2' , C4, C6-confirmed-invalid, C3', or C13).
Flow: Once quorum_invalid is reached, the collector assembles an evidence bundle consisting of: (i) the refs into Diff for MsgStartInference / MsgSkipProbe , CarrySkip , and (for C13) the H_i -slot window; (ii) the set of CPoCVote messages achieving quorum; (iii) the relevant schedule inputs ( PoC_slot_set , Schedule at heights in I ). This bundle is handed to FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md (./FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md) for inclusion in the finalization bundle for mainnet — the bundle is the input to slashing.
C12 — Executor / schedule desync (verifier bug)
Setup: V has stale PoC_slot_set or wrong epoch schedule (not the network majority view).
Expected behavior: V is at fault for mis-verdict; this is a node-operator / epoch-refresh issue, not host fault. Recovery belongs to the schedule/epoch layer (out of scope). The protocol must log the conflict so operators can detect it; it must not penalize H_i when only an outlier V disagrees.
C13 — Developer withholds work from a ready host (routing misbehavior)
Setup: Some host H_i has signalled ready (either via CPoCProbeResponse(outcome = ready) carried in Diff at some nonce N_ready , or because Schedule(H_i, H) = idle across the last W_ready mainnet blocks that every verifier strictly confirms). The developer is nonetheless not routing real inference to H_i :
- at nonces where executor(n) = H_i (i.e. n mod N_slots = i ), D keeps sending MsgSkipProbe(target = H_i) rather than MsgStartInference , or
- D stops emitting messages at H_i -slot nonces altogether while continuing to send to other slots.
Observation (at each V ). V counts, over a trailing window of W_fair rounds ending at the current nonce:
n_inf(H_i) = MsgStartInference entries with executor(n) = H_i ,
n_probe(H_i) = MsgSkipProbe entries targeted at H_i ,
- whether H_i is ready (per ready_at[H_i] receipt or Schedule(H_i, H) = idle for every H ∈ [h_start_window, H(V)] ).
Violation predicate. ready(H_i) ∧ n_probe(H_i) + n_miss(H_i) ≥ θ_fair ∧ n_inf(H_i) < θ_min_inf — i.e. over the window, D sent probes or left H_i -slots empty at least θ_fair times while sending fewer than θ_min_inf real inferences to H_i , despite H_i being ready. Exact values (W_fair, θ_fair, θ_min_inf) are chain-parametrized (TBD; see Open questions).
Flow:
1. Diff[N_ready] : CarrySkip wrapping CPoCProbeResponse(outcome=ready) for H_i → every V records ready_at[H_i] = (N_ready, h_ready) 2. Nonces N_ready+1 … N_ready+W_fair·N_slots advance: V tallies n_inf(H_i), n_probe(H_i) at H_i-slot nonces from Diff 3. Violation predicate fires at V: V enters "withholding-alert" state for (D, H_i) 4. Downstream enforcement: every V that is itself a future executor for D refuses to serve D's requests (returns a new p2p signal `RouteFairnessRefusal(D, H_i, evidence_refs)`) until: (a) D issues MsgStartInference(executor = H_i) AND H_i confirms it (MsgConfirmStart), OR (b) H_i re-enters cPoC (signals active/prepare via a fresh CPoCProbeResponse or via Schedule(H_i, H) transitioning back to {active, prepare}). 5. When (a) or (b) holds, V clears the withholding-alert and resumes serving D.
Expected verdict: **Invalid against the developer**, not against any host. Evidence: ready_at[H_i] receipt + the H_i -slot window of Diff showing probes / empty slots but no inference requests.
Why enforcement sits with "next hosts". The only actor that can credibly deny D further service is the host queued to execute D's next request. If those hosts refuse until D resumes fair routing, D has a direct economic incentive to stop withholding. No mainnet round-trip is required in the hot path; the decision is local at each V from the same Diff contents, so every honest host reaches the same alert.
Open parameters (deferred to Open questions):
- W_fair , θ_fair , θ_min_inf thresholds.
- Whether a ready_at receipt decays after the host re-enters cPoC (presumably yes — once Schedule(H_i, H) = active again, old receipts are cleared).
- Precise wire format of RouteFairnessRefusal and whether it also lands in Diff as evidence for slashing D's stake.
C14 — Low-load strategic delay (developer heartbeat mitigation)
Applicability: Only possible on low session load — specifically, when Diff contains no signed entries between R_req and N_carry that would otherwise tighten V's upper bound h_high on R_req 's true height. On any session with concurrent inference traffic, intermediate entries auto-tighten the band and this attack surface closes by itself.
Setup. Schedule(H_i, h_req) = idle (host is not on cPoC at the moment R_req enters Diff ). Immediately after R_req , session traffic goes quiet: D has no other inferences to submit. A malicious H_i then waits strategically for its next scheduled cPoC window to open at some height h > h_req , signs CPoCSkipResponse(R_req, active) during that later window, and relies on D 's late CarrySkip landing far enough in the future that V's height interval I = [h_X, h_carry] contains h . Under ∃ H ∈ I semantics (Verdict predicate, step 3) the carried refusal now passes, even though the host was idle at h_req and therefore owed the developer real inference.
Flow (attack, without mitigation):
mainnet h_req : Diff[R_req] = MsgStartInference # H_i idle at h_req ... quiet session; no intermediate Diff entries ... mainnet h+Δ : H_i enters cPoC at mainnet height h > h_req H_i → D : CPoCSkipResponse(R_req, active) # signed at height h (fresh lie) mainnet h_carry: Diff[N_carry] = CarrySkip(embeds above) V on ingest: h_X ≈ h_req; h_carry ≫ h_req I = [h_X, h_carry] — wide band, no intermediate stamp ∃ H ∈ I : Schedule(H_i, H) = active ⇒ step 3 passes → Valid (wrong)
Mitigation (developer heartbeat). When D has an outstanding R_req and no further inference to submit within the current round ( R_req … R_req + N_slots ), D SHOULD emit a lightweight heartbeat — a MsgSkipProbe targeted at the natural next slot executor(R_req + 1) — within ≈ 1 mainnet block of R_req . The heartbeat carries D 's signed observed_height ≈ h_req , and the host's responding CPoCProbeResponse (carried back via a subsequent CarrySkip ) carries the host's signed observed_height as well. Both stamps land in Diff at nonces > R_req , providing a tight upper bound h_high on R_req 's true height.
Cadence — one heartbeat, one round, only while idle.
- One-shot per quiet window. D emits the heartbeat once within the round of R_req . A single stamped entry is sufficient to tighten h_high ; additional heartbeats add no verdict strength.
- Scoped to the round of R_req . Once the session advances past nonce R_req + N_slots (one full executor round), the band for R_req is already bounded from above by any signed entry in that window. D MUST NOT continue emitting heartbeats after the round closes — further ones no longer improve the verdict for R_req .
- Conditional on absence of real traffic. Heartbeats are only needed when D would otherwise leave Diff quiet. If D has real MsgStartInference traffic queued (any nonce in [R_req + 1, R_req + N_slots] ), those entries already provide h_high via their own observed_height stamps — no heartbeat is emitted.
Flow (mitigated):
mainnet h_req : Diff[R_req] = MsgStartInference(to H_i) # real request mainnet h_req+ε : Diff[R_req+1] = MsgSkipProbe(to H_{i+1}) # heartbeat — if no real follow-up mainnet h_req+ε' : H_{i+1} → D : CPoCProbeResponse(N_SP=R_req+1, …) mainnet h_req+ε" : Diff[N_hb_carry] = CarrySkip(embeds the probe response) # observed_height stamps ≈ h_req ... (D stops heartbeating; round closes) ... mainnet h_carry : Diff[N_carry] = CarrySkip(for the real R_req) V on ingest of Diff[N_carry]: h_X = height_at[X] (≈ h_req; lower bound) h_high = observed_height on earliest stamp in (≈ h_req+ε; heartbeat tightened) Diff[(R_req, N_carry)] band = [h_X, h_high] — collapses to ≈ {h_req} step 3 now evaluates against a near-point band: Schedule(H_i, h_req) = idle ⇒ Invalid (attack closed)
Interaction with other cases.
- If the heartbeat is targeted at H_i itself and H_i responds ready , the response contradicts its own later CPoCSkipResponse(R_req, active) — a double-claim analogous to the MsgConfirmStart vs. CPoCSkipResponse mutual-exclusion rule. Verdict is Invalid against H_i on sight, without needing the band to resolve.
- If the heartbeat is targeted at the next-slot host H_{i+1} (the natural case since R_req + 1 's executor is H_{i+1} ), C13's withholding detector MUST exempt heartbeat probes emitted while an R_req awaits verdict — the probe is height-sync machinery, not a sustained routing pattern. See Open questions.
- If D fails to emit a heartbeat despite having no alternative traffic, the band stays wide and the fresh-lie attack succeeds under ∃ H ∈ I . The heartbeat is therefore a developer-side obligation , not a protocol-enforced one from the host's perspective; a careless or lazy D exposes itself to being lied to. This aligns incentives: heartbeating protects D 's own payment for real work.
Expected verdict: With the heartbeat in place, the same CPoCSkipResponse that would have strategically passed under a wide band now fails step 3 and is settled Invalid via the standard CPoCVote quorum (§ Consensus / voting). Without the heartbeat on a low-load session, the protocol's verdict fidelity degrades gracefully — the verdict is whatever ∃ H ∈ I returns on the wide band — and settlement-layer penalties on host withholding remain the only recourse.
Open parameters (deferred to Open questions):
- The exact spacing between R_req and the heartbeat (≈ 1 mainnet block is a suggestion; could be tighter or looser).
- Whether the heartbeat must be a MsgSkipProbe or a dedicated lightweight message without a response expectation. MsgSkipProbe is reused here because it already carries an observed_height and rides existing Diff wire formats, but a response-free variant is cheaper.
- The exemption rule carving heartbeats out of C13's withholding tally.
Consensus / voting
Every verifier V computes the Verdict predicate (§ Data flow) independently against its local view of Diff and H(V) . When Verdict ∈ {Invalid, Inconclusive-pending-confirmation} (or a C13 developer-withholding alert fires), V signs and emits a CPoCVote for that N_carry . For target = host(H_i) , votes are addressed to D as collector (this release). For target naming D (C3′, C13), trusted aggregation is not specified here — see § Consensus / voting (optimistic gap until self-finalization). A verdict is settled for finalization only after a quorum of independent votes has been collected; an individual verifier's opinion, by itself, slashes nobody.
CPoCVote (new p2p message, then into finalization bundle)
A single CPoCVote is cheap; the flood size is bounded because only verifiers with a non- Valid local verdict emit one, and every one is a pointer into existing Diff entries.
Collector: this release vs. self-finalization (including votes against D )
Host-fault cases ( target = host(H_i) — C2, C2′, C4, C6, etc.). The developer D is the vote collector for this release:
- D already owns the CarrySkip envelope and knows which N_carry the vote refers to.
- D is the economically interested party when a malicious host means D did not get served.
Collection procedure:
- Each V with a non- Valid verdict sends CPoCVote to D via p2p (optionally piggy-backed on the same channel that carries SkipEvidenceGossip ).
- D aggregates distinct signatures until |votes(Invalid)| ≥ quorum_invalid .
- D attaches the bundle to finalization per FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md (./FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md) . The vote bundle is the input to slashing.
Developer-target cases ( target names D — C3′ forged carry, C13 withholding). D cannot be the trusted aggregator of votes that would slash or dispute D . Normative intent: once self-finalization is implemented, CPoCVote s for these targets MUST be collected and aggregated in the finalization round (the same developer-independent path as other settlement), not by D .
This release — optimistic gap. The protocol does not specify a collector for developer-target votes. We assume D behaves honestly when forwarding or aggregating evidence in practice, or that C3′/C13 Invalid outcomes are out-of-band rare; malicious D censoring or withholding CPoCVote s against itself is a known uncovered negative case , scheduled for closure when self-finalization lands. Verifiers still emit CPoCVote with target = developer(D) / carrier(D) as specified; only the trusted aggregation path is deferred.
Future release (self-finalization). When the finalization round aggregates CPoCVote without relying on D :
- Each V still emits CPoCVote on the standard channel; wire format unchanged.
- The finalization round collects votes at a deterministic boundary for both host-fault and developer-fault cases, removing reliance on D for any target.
- This also removes the failure mode " D stops sending traffic and never submits a vote bundle" for host-fault cases.
Quorum, weighting, tie-breaks
Exact values — quorum_invalid (e.g. simple-majority vs. 2/3 stake-weighted), tie-break rules, stake weighting, and the mapping from votes to mainnet slashing amounts — must match the finalization / slashing layer. These are chain-parametrized and deferred to FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md (./FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md) and the mainnet slashing spec. This doc only guarantees:
- Every honest V reaches the same verdict from the same Diff + strictly-confirmed height slice (by construction of the Verdict predicate).
- Dishonest minority votes cannot flip a correct quorum, because CPoCVote includes the schedule_witness and is auditable at finalization time (a dishonest vote is itself slashable).
Open questions (for formalization)
- **PoC_slot_set provenance:** set at escrow init (immutable) vs queried post-init and cached. Different failure modes.
- **prepare policy:** is skip allowed while Schedule = prepare (treat like active ) or forbidden (treat like idle )? Chain-spec flag skip_allowed_during_prepare .
- Signing input domain separators: cPoCRefusalContent (host signature on CPoCSkipResponse , binds inference_id + reference_nonce + reason), cPoCProbeResponseContent (host signature on CPoCProbeResponse , binds probe_nonce + reference_nonce + outcome), CarrySkipContent (developer signature on CarrySkip , binds N_carry + referenced_nonce + payload_kind + host_response bytes), and the signing input for MsgSkipProbe (binds probe_nonce = N_SP + target_host_id ).
- Evidence-object layout for finalization (list of Diff -refs, signatures, schedule-witness); shared with FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md (./FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md) .
- C13 thresholds (W_fair, θ_fair, θ_min_inf) for the developer-withholding predicate: how many H_i -slot nonces of probes / empty slots vs. real inferences, over how many rounds, qualify as misbehavior? Must be tuned so that legitimate brief probing (e.g. a single confirmation probe right after ready before resuming inference) does not trigger alerts.
- **ready_at lifecycle.** When exactly does a ready receipt for H_i expire? Candidates: (a) on the first strictly-confirmed Schedule(H_i, H) ∈ {active, prepare} after the receipt; (b) on any subsequent non- ready CPoCProbeResponse / CPoCSkipResponse for H_i carried in Diff ; (c) a hard TTL in mainnet heights. Likely all three with (a) ∨ (b) ∨ (c) .
- **RouteFairnessRefusal surface.** Is this purely a p2p refusal signal between hosts, or must it also land in Diff as a signed artefact so mainnet can slash D ? If the latter, it becomes another SubnetTx variant and needs its own signing domain.
- Roundtrip-free Path B via developer unilateral skip (future release). Can the MsgSkipProbe → p2p response → CarrySkip roundtrip be eliminated by letting D place a D-signed unilateral-skip marker (e.g. MsgCPoCSkipMarker(nonce, target_host = H_i, basis = {N_prev_carry, h_prev}) ) at H_i 's slot nonce and routing the real MsgStartInference to the next slot? Requires (i) wire format for the marker and its signing domain; (ii) a freshness rule keyed to a prior CarrySkip for H_i — the marker is valid only while the schedule-implied cPoC window referenced by N_prev_carry has not expired at V's current height; (iii) a per-evidence cap on consecutive unilateral skips so a single old CarrySkip can't authorize indefinite skipping; (iv) reconciling with ready_at[H_i] and the C13 detector — a ready receipt invalidates outstanding marker authority immediately. Explicitly out of scope for the current release.
- Vote quorum parameters. quorum_invalid (simple majority vs. 2/3 stake-weighted), whether votes are counted per-host or stake-weighted, tie-break rules, and a liveness timeout for the collector to declare "no quorum reached, treat as Valid " are chain-parametrized and deferred to the finalization / slashing spec.
- Self-finalization collector (future release) — required for developer-target votes. When the finalization round aggregates CPoCVote without relying on D , we need: (i) a deterministic boundary condition that triggers vote aggregation (block height, session sealing, etc.); (ii) explicit ingestion of CPoCVote with target = developer(D) / carrier(D) (C3′, C13) so aggregation is not left to D ; (iii) handling for late-arriving votes across the boundary; (iv) a migration story so older nodes that still send host-fault votes to D compose with the new collector. The wire format of CPoCVote itself should not need to change — only the aggregation destination. This closes the optimistic gap documented in § Consensus / voting (malicious D censoring votes against itself). Explicitly out of scope for the current release .
- C14 heartbeat policy. (i) Exact spacing between R_req and the heartbeat (≈ 1 mainnet block proposed; tune against network latency). (ii) Whether the heartbeat reuses MsgSkipProbe or justifies a dedicated response-free lightweight SubnetTx variant (which would bind only D 's signed observed_height and incur no p2p roundtrip). (iii) Carve-out rule in C13's withholding tally for probes emitted while an R_req awaits verdict, so a legitimate heartbeat doesn't count as withholding from H_{i+1} . (iv) Whether observed_height fields are strictly required on MsgStartInference , MsgConfirmStart , MsgSkipProbe , and CarrySkip for verifier determinism, or whether V's own height_at[·] stamps suffice in practice — i.e. is C14's closure structurally in the wire format or operationally via heartbeats on top of today's messages.
- C2' seal window W_seal . Default proposed at ≈ 2 mainnet blocks (matching timeout_skip_gossip ). Needs to be tuned against (i) realistic MsgConfirmStart arrival latency after a CarrySkip , (ii) how long verifiers can reasonably buffer pending_verdicts entries in the provisional state, (iii) whether settlement-layer slashing for post-seal confirm-then-skip contradictions is strong enough to treat the seal closure as a true bound. If not, consider extending W_seal or allowing a bounded number of post-seal flips recorded as "late evidence" rather than verdict changes.
- Devshard-ingest mutual-exclusion rule (C2' defence in depth). Whether the gateway-level rejection of MsgConfirmStart when CarrySkip(payload_kind = skip_response) for the same inference_id already exists in Diff (and vice versa) is a MUST or a SHOULD . MUST simplifies verdict reasoning (step 2 scan becomes a residual safety net for the race window only) but creates a harder dependency on every ingest pipeline behaving identically; SHOULD keeps the predicate as the sole source of truth but leaves the ingest rule as an opportunistic optimization. Tie-break also affects how implementations handle a genuine race in which both messages are valid at their own arrival times.
Related documents
- HEIGHT_SYNC_PROTOCOL_PROPOSAL.md (./HEIGHT_SYNC_PROTOCOL_PROPOSAL.md) — out of scope for this doc; supplies H(V) as a black-box oracle.
- FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md (./FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md) — consumes Invalid verdicts, decides inclusion in finalization bundles.
протокол пропуска cPoC (devshard) — предложение
Резюме
В devshard хосты, на которых выполняется PoC подтверждения (cPoC), не должны обслуживать обычный вывод в течение срока действия своих обязательств PoC. Другие хосты должны иметь возможность определить, является ли пропуск законным (пропускающий хост находится в расписании cPoC на соответствующей высоте) или неправомерным (лжет или отказывается от работы). Этот документ определяет поток данных и случаи, которые должен обрабатывать протокол cPoC.
Вне области применения этого документа:
Статус: черновик — поток данных + случаи, указанные ниже; схемы проводов, цепные крючки и предикаты разрезания еще не определены.
Область применения
Общие предположения (информативно)
Обозначения (одноразовые номера, используемые повсюду)
Все одноразовые номера ниже являются монотонными индексами в Diff (поток данных § локальное состояние для каждого сеанса). Они определены здесь один раз, чтобы в последующих разделах можно было ссылаться на них без повторного введения каждого из них.
Постановка задачи
1. Пропустить корректность
Хост, который возвращает «пропуск из-за cPoC», может быть:
Честный — Schedule(H_i, H) ∈ {prepare, active} и H_i ∉ PoC_slot_set, или
Протокол должен позволять каждому честному верификатору **V ** выносить один и тот же вердикт из одного и того же diff, используя **H(V) ** в качестве оракула высоты (предположение 1).
2. Разработчик воспроизводит/удерживает
Разработчик может сохранить ответ пропуска cPoC хоста, а затем прикрепить его через CarrySkip. Смягчение является многоуровневым:
3. Объем сплетен
При высокой скорости вывода, если большинство хостов пропускают время cPoC, сплетни о каждом пропуске неприемлемы:
Принципы проектирования (высокий уровень)
Формализация в § Поток данных и случаи в § Обрабатываемые случаи выбраны так, чтобы удовлетворить следующим принципам. Символы Nonce (R_req, N_SP, N_carry, X, N_slots) определены в разделе Общие предположения → Нотация; H(V) , Schedule и PoC_slot_set в пунктах 1–3 общих предположений; timeout_skip_gossip в разделе § Минимизация сплетен.
Два пути запроса
При открытии запроса разработчик выбирает одну из двух форм; оба сходятся к одному и тому же предикату Verdict.
Путь А — вывод с возможным отказом от cPoC (полная полезная нагрузка). Разработчик отправляет реальный запрос на вывод; хост либо подтверждает и запускает его, либо отказывается из-за cPoC.
D → devshard : MsgStartInference(R_req, Prompt_hash, …) [в Diff в R_req] счастливый путь → H_i → devshard : MsgConfirmStart(R_req) [в Diff] → … → MsgFinishInference путь cPoC → H_i → D : CPoCSkipResponse(R_req, причина) [p2p; НЕ в Diff] D → devshard : CarrySkip(N_carry, <встроенный CPoCskipResponse>) [в Diff в N_carry]Путь B — облегченная проверка пропуска (без подсказки, без затрат на вывод). Разработчик просит H_i сообщить о своем состоянии cPoC, не платя подсказки. Хост не выполняет вывод; он просто возвращает подписанный статус. Ответ имеет два возможных результата:
D → devshard : MsgSkipProbe(N_SP) [в Diff в N_SP] H_i → D : CPoCProbeResponse(N_SP, результат ∈ {cpoc_active, cpoc_prepare, готов}) [p2p; НЕ в Diff] D → devshard : CarrySkip(N_carry, <embedded CPoCProbeResponse>) [into Diff at N_carry] # N_SP < N_carry строго (две отдельные записи Diff)Готовый результат, перенесенный в Diff, не является пропуском Invalid — предикат вердикта просто не применяется (отсутствие отказа в проверке). Вместо этого это уведомление о планировании: V записывает, что H_i сообщил о готовности на высоте в [h_X, h_carry] . Последующее поведение разработчика проверяется по этому получению с помощью C13 (разработчик продолжает проверять/пропускать готовый хост — см. § Случаи).
Ключевые инварианты, общие для обоих путей:
Все нижеприведенное — привязка nonce, минимизация сплетен, поток данных, случаи — одинаково применимо к обоим путям; единственная специализация пути B — это дополнительный готовый результат (и его последующий случай C13).
Nonce-привязка (свежесть в интервале высоты)
Из-за асинхронного трафика разработчиков (Общие предположения, пункт 5) ответ на запрос, отправленный в nonce **R_req **, может появиться в Diff только в nonce **R_req + x** , **x ≥ 0** . Задержка x не ограничена количеством раундов — раунды могут быть намного быстрее, чем блоки основной сети или ответ хоста, поэтому между R_req и N_carry может пройти много раундов. Таким образом, вердикты привязываются к интервалу высот, который каждый верификатор конструирует локально на основе своих собственных наблюдений за Diff :
Минимизация сплетен
Параметр **timeout_skip_gossip ** (предложение: ≈ 2 блока основной сети) параметризуется цепочкой; его точное значение здесь выходит за рамки.
Поток данных (формализованный)
Вечеринки
Локальное состояние для каждого сеанса (на каждом **V** )
Примитивы
Имена в subnet/proto/subnet/v1/{tx,diff}.proto, если не отмечено (новое). Входной набор предиката вердикта — MsgStartInference, MsgConfirmStart, MsgSkipProbe и CarrySkip; входной набор для расчета вердикта — CPoCVote; остальные сообщения являются носителями p2p ( CPoCSkipResponse , CPoCProbeResponse ), сообщениями о доставке ( SkipEvidenceGossip ) или окончательным расчетом ( MsgTimeoutInference{…CPOC} ).
Сквозной поток (счастливый путь, фактически хост на cPoC)
nonce R_req nonce R_req+1..N_carry-1 D ────────────── InferenceRequest(R_req) ─────────────▶ H_i (другие запросы к H_{i+1..}) │ H_i в cPoC │ D ◀─────────── CPoCSkipResponse(R_req, причина) ──────────┘ (приходит асинхронно, R_req + x в Diff) D ───── CarrySkip(N_carry) встраивает CPoCSkipResponse(R_req, …) ──▶ любой хост ──▶ Diff[N_carry] каждый V наблюдает за L: при приеме Diff[N_carry]: запись height_at[N_carry] = H(V) оцените Verdict(…) с использованием H(V) и правил nonce-окна нижеПредикат вердикта (нормативная форма)
V вычисляет **Вердикт(skip_evidence) ∈ {Действительный, Недействительный, Неубедительный}** как:
Выходные данные подаются в минимизацию Gossip (ниже) и для споров в FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md (./FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md).
Дела для обработки (дело/поток данных)
Легенда: R_req = nonce-запрос вывода пути A (или, в пути B, псевдоним зондового nonce N_SP ); N_carry = nonce, при котором CarrySkip добавляется к Diff; оба пути строго имеют R_req < N_carry. R обозначает раунд исполнителя размером N_slots.
C1 — Честный пропуск, честный разработчик (счастливый путь)
Настройка: Schedule(H_i, H(V)) = active, H_i ∉ PoC_slot_set, устройство ведет себя нормально.
Поток:
D → H_i : InferenceRequest(R_req) H_i → D : CPoCSkipResponse(R_req, active) D → H_{i+1} : следующий InferenceRequest в R_req+1, несущий пропущенный BLOB-объект (или отдельный CarrySkip при некотором N_carry ≥ R_req) V (= любой хост): on Diff[N_carry] → Verdict = Valid (окно nonce + расписание)Ожидаемый вердикт: **Действительно**. Никаких сплетен, никаких триггеров финализации.
C2 — Вредоносный хост, ложный пропуск
Настройка: Schedule(H_i, H(V)) = IDLE, H_i ∉ PoC_slot_set, но H_i отвечает CPoCskipResponse, чтобы избежать работы.
Порядок действий: такой же, как C1, до момента, когда разработчик публикует CarrySkip. Каждый хост V тогда:
V on Diff[N_carry]: Compute Verdict(...) = Invalid # Проверка расписания не удалась, когда я испускаю CPoCVote(N_carry, verdict = Invalid, Signed_by=V) # p2p к D (и, возможно, сплетни) D собирает сообщения CPoCVote от разных хостов: if |votes(Invalid)| ≥ quorum_invalid: вердикт выносится, поскольку Invalid D передает пакет голосов на финализацию (сегодня) — ИЛИ — хосты публикуют голоса в следующем раунде финализации (будущий выпуск; см. § Консенсус/голосование)Ожидаемый вердикт: недействительно (проверка расписания не удалась на интервале высот I). Недействительный результат не связан с завершением одной из сторон; это кворум CPoCVote от хостов, которые наблюдали Diff[N_carry] и независимо вынесли тот же вердикт. См. § Консенсус/голосование по протоколу сбора голосов и разделение «разработчик сегодня / самодоработка завтра».
C2' — мошенничество с двойной претензией (подтвердите и пропустите один и тот же запрос)
Настройка: H_i подписывает как MsgConfirmStart, так и CPoCSkipResponse для одного и того же inference_id (напрямую или через D, переносящего пропуск пропуска). Эти два сообщения криптографически несовместимы: MsgConfirmStart.executor_sig обязывает H_i выполнить вывод, а встроенный CPoCSkipResponse обязывает H_i отклонить его. Применимо только к пути A (payload_kind =skip_response); Путь B не имеет inference_id в переносе и не может инициировать этот случай.
Расход (подтвердите перед переносом):
Поток (сначала пропустите, подтверждение поступает в окно уплотнения):
Поток (подтверждение приходит после окна уплотнения):
Diff[N_carry] : CarrySkip(...) # запечатан Действителен после W_seal Diff[N_confirm] : MsgConfirmStart(I, H_i) # слишком поздно, чтобы изменить вердикт cPoC V: НЕ повторно открывает вынесенный вердикт; вместо этого нарушение протокола передается на рассмотрение (завершение) как отдельное доказательство того, что Х_и подписал два противоречивых заявления о выводе I.Ожидаемый вердикт: **Недействительно против H_i ** всякий раз, когда оба артефакта попадают в Diff в пределах окна печати друг друга. Урегулируется через стандартный кворум CPoCVote (§ Консенсус/голосование), при этом пакет голосов содержит код причины ∈ {double_claim_confirm_then_skip, double_claim_skip_then_confirm} и указатели на обе записи Diff в качестве криптографического доказательства противоречия. За пределами окна печати нарушение по-прежнему может быть разрезано, но на уровне расчета, а не в виде переворота предиката cPoC (сохраняет ограниченность окончательности вердикта).
Дополнительное усиление защиты от devshard-ingest. Осколок разработчика МОЖЕТ отказаться добавлять любое сообщение, если другое уже существует в Diff (предикат вердикта, шаг 2, «Эшелонированная защита при приеме»). В обычном случае это переносит отклонение с уровня предикатов на уровень шлюза; Шаг 2 предиката остается в силе в течение окна гонки, в течение которого оба сообщения могут законно одновременно поступить на уровень приема.
C3 — Поздний перенос разработчика (настоящий пропуск, опоздание)
Настройка: H_i вернул допустимый CPoCskipResponse в R_req во время своего окна cPoC (высота H_skip). Разработчик удерживает большой двоичный объект в течение произвольного количества раундов, а затем выдает CarrySkip по адресу N_carry ≫ R_req.
Поток:
Ожидаемый вердикт: **Действительно**. Свидетельство хозяина правдиво для высоты I; опоздание не делает это задним числом ложью. Любой остаточный ущерб (запись вывода остается открытой, расчет застопорился) обрабатывается на уровне расчета ( MsgTimeoutInference{…CPOC} и сроки завершения), а не предикатом вердикта cPoC.
C3' — Ошибка причинно-следственной связи (поддельный перенос)
Настройка: разработчик публикует CarrySkip с N_carry < R_req (ссылается на запрос, который еще не вошел в Diff).
Поток: Шаг (1) предиката вердикта отклоняет конверт неравенства причинности R_req ≤ N_carry .
Ожидаемый вердикт: **Недействительно** в отношении оператора связи (подпись разработчика на CarrySkip), а не в отношении H_i. Это чистая проверка на подделку, не зависящая от какого-либо интервала высот.
C4 — POC_SLOT = истинный хост возвращает пропуск
Настройка: H_i ∈ PoC_slot_set (освобожден от вывода во время cPoC других), но отвечает CPoCskipResponse .
Поток: любой обычный запрос/ответ, приводящий к пропуску переноса.
Ожидаемый вердикт: недействительно (проверка роли не удалась). Вердикт выносится кворумом голосов (см. C2/§ Консенсус/голосование): каждый хост вычисляет один и тот же Invalid и генерирует CPoCVote ; собранный пакет — это доказательства, переданные на разрезание (H_i).
C5 — Пропустить окно подготовки
Настройка: Schedule(H_i, H(V)) = подготовить (зависит от политики).
Решение: Те же правила вердикта, что и для active .
C6 — Безрезультатно из-за неопределенности высоты.
Настройка: Schedule(H_i, H(V)) =dleid , но уровень синхронизации по высоте еще не строго подтвердил H(V) для nonce-окна (для нас это выходит за рамки — мы только потребляем его сигнал).
Поток: шаг вердикта (4) возвращает Inconclusive .
Ожидаемое действие: V еще не отправляет CPoCVote; он ждет подтверждения от слоя высоты. Если подтвержден Invalid , V выдает CPoCVote(Invalid), и стандартный поток голосования-кворума (§ Консенсус/голосование) собирает пакет. Если подтверждено Valid , голосование не проводится и никакие действия не предпринимаются.
C7 — Пропустить проверку (Путь B), результат отказа
Настройка: D хочет проверить статус cPoC от H_i без отправки запроса. Schedule(H_i, H) ∈ {active, подготовить} на высоте, на которой получен ответ на зонд.
Поток:
D → devshard : MsgSkipProbe(N_SP, target = H_i) # в Diff в N_SP H_i → D : CPoCProbeResponse(N_SP, result ∈ {cpoc_active, cpoc_prepare}) # p2p, подписанный H_i D → devshard : CarrySkip(N_carry, CPoCProbeResponse) # в Diff в N_carry > N_SP V on Diff[N_carry]: R_req := N_SP запускает предикат Verdict (шаги 1–5) без измененийОжидаемый вердикт: **Действительно** (тот же предикат, что и для пути A, применяется с R_req := N_SP ).
C7' — Пропустить зонд (Путь B), готовый результат
Настройка: D исследует H_i . H_i завершил свое окно cPoC и находится в состоянии READY_INFERENCE ( Schedule(H_i, H) = бездействие на высоте ответа).
Поток:
D → devshard : MsgSkipProbe(N_SP, target = H_i) H_i → D : CPoCProbeResponse(N_SP, результат = готов) # p2p, подписанный H_i D → devshard : CarrySkip(N_carry, CPoCProbeResponse) # в Diff при N_carry > N_SP V on Diff[N_carry]: обнаружить payload_kind = Probe_response AND result = готовая запись, планирование планирования: Ready_at[H_i] = (N_carry, h_carry) Шаги предиката вердикта (2–3) НЕ применяются (нет отказа в оценке)Ожидаемый вердикт: неприменимо. Перенос — это квитанция планирования, а не подтверждение пропуска. Это обязывает разработчика возобновить маршрутизацию реального MsgStartInference в H_i при последующих одноразовых значениях H_i-slot. Постоянное отклонение после этого поступления вызывает срабатывание C13.
C8 — Нет ответа вообще (тайм-аут)
Настройка: H_i ничего не возвращает (ни вывод, ни пропуск).
Ожидаемые действия: выходят за рамки вердикта cPoC-skip. Управляется **USER_TIMEOUT ** в FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md (./FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md). Протокол cPoC в этом случае не выносит вердикта.
C9 — Сбор голосов с низкой нагрузкой (откровенные сплетни)
Настройка: после timeout_skip_gossip разница не продвинулась на один полный раунд, поэтому не каждый V обязательно видел перенос переноса, а сборщик голосов (см. § Консенсус/голосование) еще не достиг quorum_invalid .
Поток:
V1 отправляет SkipEvidenceGossip(Diff-refs) одноранговым узлам # отстающие одноранговые узлы догоняют одноранговые узлы Diff, восстанавливают Diff-refs, локально вычисляют Verdict и выдают CPoCVote, если их вердикт недействителен. Сборщик объединяет голоса (`D` для случаев сбоя хоста в этом выпуске; финальный раунд для голосов целевых разработчиков, когда существует самофинализация — см. § Консенсус/голосование)Ожидаемый вердикт: независимо от того, что заявит кворум голосований на основе тех же доказательств различий. SkipEvidenceGossip предназначен только для облегчения доставки; он не вычисляет вердикт, он просто делает видимым тот же CarrySkip, чтобы отстающие участники могли голосовать.
C10 — Элисионный раунд с высокой нагрузкой
Настройка: Высокая частота запросов; разница естественным образом проходит мимо R_req + N_slots в течение timeout_skip_gossip.
Ожидаемое действие: эмиссия SkipEvidenceGossip не требуется; каждый V имеет доказательства по своей конструкции. Каждый V независимо вычисляет Verdict и, если он недействителен, выдает CPoCVote. Сборщик суммирует голоса как обычно.
C11 — Пакет доказательств спорного уровня
Настройка: вердикт недействителен (C2, C2', C4, C6-подтверждено-недействительно, C3' или C13).
Порядок действий: как только достигается quorum_invalid, сборщик собирает пакет доказательств, состоящий из: (i) ссылок на Diff для MsgStartInference/MsgSkipProbe, CarrySkip и (для C13) окна H_i -slot; (ii) набор сообщений CPoCVote, достигающих кворума; (iii) соответствующие входные данные расписания ( PoC_slot_set , Расписание на высотах в I ). Этот пакет передается FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md (./FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md) для включения в пакет финализации для основной сети — пакет является входными данными для слэшинга.
C12 — Рассинхронизация Исполнителя/расписания (ошибка верификатора)
Настройка: V имеет устаревший PoC_slot_set или неправильное расписание эпох (не представление большинства сети).
Ожидаемое поведение: Ви виноват в неправильном приговоре; это проблема оператора узла / обновления эпохи, а не ошибка хоста. Восстановление относится к уровню расписания/эпохи (выходит за рамки). Протокол должен регистрировать конфликт, чтобы операторы могли его обнаружить; он не должен наказывать H_i, если только выброс V не согласен.
C13 — разработчик приостанавливает работу с готового хоста (неправильное поведение маршрутизации)
Настройка: какой-то хост H_i подал сигнал о готовности (либо через CPoCProbeResponse(outcome = Ready), передаваемый в Diff в какой-то момент N_ready , либо потому, что Schedule(H_i, H) = бездействие в последних блоках основной сети W_ready, что строго подтверждает каждый верификатор). Тем не менее, разработчик не направляет реальный вывод в H_i :
Наблюдение (на каждом V). V подсчитывается в конечном окне раундов W_fair, заканчивающихся текущим nonce:
n_inf(H_i) = записи MsgStartInference с executor(n) = H_i ,
n_probe(H_i) = записи MsgSkipProbe, нацеленные на H_i ,
Предикат нарушения. Ready(H_i) ∧ n_probe(H_i) + n_miss(H_i) ≥ θ_fair ∧ n_inf(H_i) < θ_min_inf — т.е. через окно D отправил зонды или оставил H_i -слоты пустыми как минимум θ_fair раз, отправляя менее θ_min_inf реальных выводов в H_i, несмотря на то, что H_i готов. Точные значения (W_fair, θ_fair, θ_min_inf) параметризуются цепочкой (TBD; см. Открытые вопросы).
Поток:
Ожидаемый вердикт: **Недействительно для разработчика**, а не для какого-либо хоста. Доказательство: квитанция Ready_at[H_i] + окно H_i -slot Diff, показывающее зонды/пустые слоты, но никаких запросов на вывод.
Почему принудительное исполнение осуществляется «следующими хостами». Единственный субъект, который может достоверно отказать D в дальнейшем обслуживании, — это хост, поставленный в очередь для выполнения следующего запроса D. Если эти хосты откажутся до тех пор, пока D не возобновит справедливую маршрутизацию, у D будет прямой экономический стимул прекратить удержание. По горячему пути не требуется двусторонний обмен данными по основной сети; решение является локальным для каждого V из одного и того же содержимого Diff, поэтому каждый честный хост получает одно и то же предупреждение.
Открытые параметры (отложены до открытых вопросов):
C14 — Стратегическая задержка при низкой нагрузке (подавление пульса разработчика)
Применимость: возможно только при низкой нагрузке сеанса, в частности, когда Diff не содержит подписанных записей между R_req и N_carry, которые в противном случае сузили бы верхнюю границу V h_high на истинной высоте R_req. В любом сеансе с одновременным трафиком вывода промежуточные записи автоматически сужают полосу, и эта поверхность атаки закрывается сама собой.
Настройка. Schedule(H_i, h_req) = бездействие (хост не находится в cPoC в момент, когда R_req входит в Diff). Сразу после R_req трафик сеанса затихает: D не имеет других выводов для отправки. Затем злонамеренный H_i стратегически ожидает открытия своего следующего запланированного окна cPoC на некоторой высоте h > h_req , подписывает CPoCSkipResponse(R_req, active) во время этого более позднего окна и полагается на позднюю посадку CarrySkip D достаточно далеко в будущем, чтобы интервал высот V I = [h_X, h_carry] содержал h . В соответствии с семантикой ∃ H ∈ I (предикат вердикта, шаг 3) переносимый отказ теперь проходит, даже если хост простаивал в момент h_req и, следовательно, должен был сделать реальный вывод разработчику.
Поток (атака, без смягчения):
mainnet h_req : Diff[R_req] = MsgStartInference # H_i простаивает в h_req ... тихий сеанс; нет промежуточных записей Diff ... mainnet h+Δ : H_i входит в cPoC на высоте основной сети h > h_req H_i → D : CPoCSkipResponse(R_req, active) # подписано на высоте h (свежая ложь) mainnet h_carry: Diff[N_carry] = CarrySkip(встраивает выше) V при приеме: h_X ≈ h_req; h_carry ≫ h_req I = [h_X, h_carry] — широкая полоса, без промежуточной отметки ∃ H ∈ I : Schedule(H_i, H) = активно ⇒ шаг 3 проходит → Валидно (неверно)Смягчение (сигнал разработчика). Когда у D есть невыполненный R_req и нет дальнейших выводов для отправки в текущем раунде ( R_req … R_req + N_slots ), D СЛЕДУЕТ выдавать легкий контрольный сигнал — MsgSkipProbe, нацеленный на естественного исполнителя следующего слота (R_req + 1) — в пределах ≈ 1 блока основной сети от R_req . Контрольный сигнал несет подписанную наблюдаемую_высоту ≈ h_req D, а ответный CPoCProbeResponse хоста (переносимый обратно через последующий CarrySkip) также несет подписанный наблюдаемый_высоту хоста. Оба штампа попадают в Diff по адресу nonce > R_req, обеспечивая жесткую верхнюю границу h_high для истинной высоты R_req.
Каденс — одно сердцебиение, один раунд, только во время простоя.
Поток (уменьшенный):
mainnet h_req : Diff[R_req] = MsgStartInference(to H_i) # реальный запрос mainnet h_req+ε : Diff[R_req+1] = MsgSkipProbe(to H_{i+1}) # Heartbeat — если нет реальной последующей основной сети h_req+ε' : H_{i+1} → D : CPoCProbeResponse(N_SP=R_req+1, …) mainnet h_req+ε" : Diff[N_hb_carry] = CarrySkip(встраивает ответ зонда) # отметки наблюдаемой_высоты ≈ h_req ... (D перестает пульсировать; раунд закрывается) ... mainnet h_carry : Diff[N_carry] = CarrySkip(для настоящего R_req) V on прием Diff[N_carry]: h_X = height_at[X] (≈ h_req; нижняя граница) h_high = наблюдаемая_высота на самой ранней отметке в (≈ h_req+ε; пульс сжат) Diff[(R_req, N_carry)] Band = [h_X, h_high] — сворачивается до ≈ {h_req}. Шаг 3 теперь оценивается по полосе, близкой к точке: Schedule(H_i, h_req) = простой ⇒ Недействительно (атака закрыта)Взаимодействие с другими делами.
Ожидаемый вердикт: при наличии контрольного сигнала тот же CPoCskipResponse, который стратегически прошел бы в широкой полосе, теперь не проходит шаг 3 и считается недействительным через стандартный кворум CPoCVote (§ Консенсус/голосование). Без тактового сигнала в сеансе с низкой нагрузкой точность вердикта протокола плавно ухудшается — вердиктом является то, что ∃ H ∈ I возвращает в широкой полосе — и штрафы на уровне расчетов за удержание хоста остаются единственным выходом.
Открытые параметры (отложены до открытых вопросов):
Консенсус/голосование
Каждый верификатор V вычисляет предикат Verdict (§ Поток данных) независимо от своего локального представления Diff и H(V) . Когда Verdict ∈ {Invalid, Inconclusive-pending-confirmation} (или срабатывает предупреждение разработчика об удержании C13), V подписывает и отправляет CPoCVote для этого N_carry . Для target = host(H_i) голоса адресуются D как сборщику (в этом выпуске). Для целевого именования D (C3', C13) доверенная агрегация здесь не указана — см. § Консенсус/голосование (оптимистический разрыв до самофинализации). Приговор принимается к окончательному вынесению только после того, как будет собран кворум независимых голосов; мнение отдельного проверяющего само по себе никого не ранит.
CPoCVote (новое p2p-сообщение, затем в пакет финализации)
Один CPoCVote стоит дешево; размер потока ограничен, поскольку только верификаторы с недействительным локальным вердиктом выдают один, и каждый из них является указателем на существующие записи Diff.
Коллекционер: этот релиз против самодоработки (включая голоса против D)
Случаи сбоя хоста (цель = хост(H_i) — C2, C2', C4, C6 и т. д.). Разработчик D является сборщиком голосов за этот выпуск:
Процедура сбора:
Случаи с разработчиком-мишенью (целевые имена D — поддельный перенос C3’, удержание C13). D не может быть доверенным агрегатором голосов, который мог бы сократить или оспорить D. Нормативное намерение: после реализации самофинализации CPoCVote для этих целей ДОЛЖНЫ быть собраны и агрегированы в раунде финализации (тот же независимый от разработчика путь, что и для других расчетов), а не D .
Этот релиз — оптимистический разрыв. В протоколе не указан сборщик голосов, предназначенных для разработчиков. Мы предполагаем, что D ведет себя честно при передаче или агрегировании доказательств на практике, или что недействительные результаты C3'/C13 встречаются за пределами диапазона редко; Злонамеренная цензура D или удержание CPoCVote против самого себя — это известный раскрытый негативный случай, который планируется закрыть, когда произойдет самозавершение. Верификаторы по-прежнему выдают CPoCVote с target = Developer(D)/carrier(D), как указано; откладывается только доверенный путь агрегации.
Будущий выпуск (самодоработка). Когда финальный раунд объединяет CPoCVote без использования D:
Кворум, взвешивание, тай-брейки
Точные значения — quorum_invalid (например, простое большинство или простое большинство). 2/3 с учетом доли), правила тай-брейка, взвешивание ставок и сопоставление голосов с суммами сокращения в основной сети — должны соответствовать уровню финализации/сокращения. Они параметризуются по цепочке и откладываются в FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md (./FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md) и спецификации сокращения основной сети. Этот документ гарантирует только:
Открытые вопросы (для формализации)
Связанные документы