devshard improvement cPoC skip protocol
#1207
alexanderkuprin
started this conversation in
Proposals
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
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.
The core idea that hosts know mainnet heights and there is (out of scope) concensus mechanism to agree on that heights. Hosts bind nonces to heights, but only to nonces they know. If host served request with
nonce_Ait binds it toH_A(host), the next nonce served by this host will benonce_B=nonce_A + slots_num. So every nonce betweennonce_Aandnonce_Bfrom the hosts view is between known to hostH_A(host)andH_B(host). And there are nonces where some other host skips the inference because of performing cPoC. So the other hosts can verify, if this skip was valid or not.Out of scope for this document:
**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.Valid/Invalid/Inconclusive) and hands evidence to FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md.It may be easier to understand this proposal through worked examples; see Cases to handle (case / dataflow).
Status: draft — data flow + cases specified below; wire schemas, chain hooks, and slashing predicates still TBD.
Scope
POC_SLOT = true(keep serving inference during cPoC windows) and the rest run cPoC when scheduled.Shared assumptions (informative)
**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 howH(V)is computed, trusted, or refreshed; see HEIGHT_SYNC_PROTOCOL_PROPOSAL.md. When this doc says “height**H” without qualification, read it as**H(V)at the momentVevaluates the case**.**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 ofpreparevsactiveare defined in the chain-side cPoC spec and out of scope here.**N_slots** slots and fixed mapping**executor(nonce) = hosts[nonce mod N_slots]**, the same logical slot recurs at**nonce + N_slots**(one round).**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 inDiff, 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.nDiffnonce.R_reqMsgStartInferenceis appended toDiff(Path A) — the inference request a cPoC skip answers.D → devshard(MsgStartInference).N_SPMsgSkipProbeis appended toDiff(Path B) — the lightweight probe that playsR_req's role when no prompt is submitted.D → devshard(MsgSkipProbe).N_carryCarrySkipenvelope (embedding either the signedCPoCSkipResponseorCPoCProbeResponse) is appended toDiff. This is the only verdict-bearing artifact inDiff; every verifier computesVerdictfromDiff[N_carry]. Causality:R_req < N_carryin Path A (distinct proto messages ⇒ distinct nonces, and the dev cannot sign the carry until after the host's p2p response exists);N_SP < N_carryin Path B for the same reason.D → devshard(CarrySkip).X≤ R_req(or≤ N_SP) whose executor isVitself;height_at[X]is V's local height observed no later thanR_reqand is the lower endpoint of the freshness intervalI = [h_X, h_carry]. Formula and derivation in § Verdict predicate, step 1.V.N_slotsexecutor(n) = hosts[n mod N_slots](assumption 4).Problem statement
1. Skip correctness
A host that returns “skipping because of cPoC” may be:
Schedule(H_i, H) ∈ {prepare, active}andH_i ∉ PoC_slot_set, orCPoC_SKIPwhile not scheduled / while inPoC_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:I = [h_X, h_carry]each verifier personally witnesses (§ Nonce binding). A late carry of a genuine skip blob remainsValid— it was truthful at a height inI; a late reveal does not retroactively make it a lie. Only skips that were never legitimate at any height inIproduceInvalid.MsgTimeoutInference{…CPOC}timeouts and finalization deadlines.3. Gossip volume
Under high inference rate, if most hosts skip during cPoC, per-skip gossip is unacceptable:
It is important that each host can participate in a lot of devshards, so gossip traffic is highly unwanted and is limited to disputes and settlement cases.
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, andPoC_slot_setin Shared assumptions items 1–3;timeout_skip_gossipunder § Gossip minimization.Two request paths
The developer chooses one of two shapes when opening a request, he can request with an inference and get cPoC skip response or can ckip in advance host that is doing cPoC; both converge on the same
Verdictpredicate.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.
Path B — lightweight skip probe (no prompt, no inference cost). Developer asks
H_ito 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:H_iis still on cPoC (cpoc_activeorcpoc_prepare); behaves like a Path-A skip for verdict purposes.H_ihas finished cPoC and isREADY_INFERENCE; the developer should resume sending realMsgStartInferencetoH_i.A
readyoutcome carried intoDiffis not anInvalidskip — the verdict predicate simply does not apply (no refusal to validate). It is instead a scheduling receipt: V records thatH_isignalledreadyat 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).Key invariants shared by both paths:
CPoCSkipResponse(Path A refusal) orCPoCProbeResponse(Path B status), the host's signed statement only enters the verifier's field of view when the developer echoes it via**CarrySkip** intoDiff.**CarrySkipis the primary verdict-bearing artifact inDiff.** EveryVcomputesVerdictfromDiff[N_carry]. For Path A, the predicate additionally scansDifffor aMsgConfirmStartmatching the sameinference_id; if one exists (in either direction relative toN_carry), the verdict isInvalidagainstH_ifor double-claim (see § Verdict predicate, step 2, and § Cases → C2').Diffentries per path. Both paths haveR_req < N_carry(resp.N_SP < N_carry) strictly: different proto messages occupy different nonces, and the developer signature onCarrySkipbinds bytes that only exist after the host's p2p response arrives.CarrySkiphas reached a finalVerdict, closing the inference record at chain level uses the existingMsgTimeoutInference{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
readyoutcome (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 inDiffonly at nonce**R_req + x**,**x ≥ 0**. The delayxis not bounded in rounds — rounds can be far faster than mainnet blocks or host response, so many rounds may legitimately elapse betweenR_reqandN_carry. Verdicts therefore bind to a height interval that each verifier constructs locally from its own observations ofDiff:**R_req**(the request it answers), stated inside the signedCPoCSkipResponse. (Term chosen to avoid collision with the height-sync "Anchor", which is out of scope here.)**N_carry**(the nonce at whichCarrySkipis appended toDiffand becomes visible to verifiers).X= the latest nonce≤ R_reqwhose executor isVitself — a nonce V personally handled, soheight_at[X]is a height V actually observed no later thanR_req. The exact formula (same round vs. previous round, depending onSP_vvs.SP_e) and its derivation are given in § Verdict predicate, step 1.I = [h_X, h_carry]whereh_X := height_at[X]andh_carry := height_at[N_carry] = H(V)at ingest ofDiff[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.Schedule(H_i, H) ∈ {prepare, active}. If no height inIplacesH_ion the cPoC schedule, the skip could not have been truthful at any momentVwitnessed →InvalidagainstH_i. A stale but genuine skip blob replayed well after the host returned toREADY_INFERENCEis stillValid— the host was legitimately refusing at some height inI; 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.)h_Xandh_carryfrom its ownheight_at[·]map; the developer's or host's claimed height inCPoCSkipResponseis informational and is not input to the verdict.Gossip minimization
timeout_skip_gossipafterN_carrythe session advances toN_carry + N_slots(one full round), every honest verifier has seen the evidence via the diff. No dedicated gossip is emitted.Vwith a non-Validverdict MAY emit a compactSkipEvidenceGossippointing intoDiff. Peers re-run the verdict predicate locally.Parameter
**timeout_skip_gossip** (proposal: ≈ 2 mainnet blocks) is chain-parametrized; its exact value is out of scope here.Data flow (conceptual)
When a host skips inference because of confirmation PoC, every other host can tell if that skip was honest or cheating—without flooding gossip.
The session is an append-only log of messages, each at a monotonic nonce. Everyone reasons from what landed in
Diff, not from private p2p alone. Verifiers see host answers only after the developer carries them intoDiff.CarrySkipis the verdict-bearing artifact.Two ways to open a skip
How verifiers judge (each host V locally)
When
VingestsDiff[N_carry]:I = [h_X, h_carry]from heightsVrecorded when it processed earlier nonces (witness nonceX≤ request, carry atN_carry).H_iactually on cPoC (prepare/active) at some height inI? If never → Invalid (lying skip). If yes → Valid (even if carried late—anti-cheat, not anti-replay).Votes & settlement
Non-Valid verdicts → signed CPoCVote → quorum / finalization.
Usually no extra gossip: the diff propagates the evidence. Gossip is a rare fallback when the session is slow to advance one full executor round.
Data flow (formalized)
Parties
**D****H_i****i**(i = nonce mod N_slots).**V**Per-session local state (at each
**V**)**Diff****n**.**H(V)****V**’s latest height-sync convergence.**height_at[n]**Vingests diff entry at nonce**n**, it records**H(V)**at that moment. Not shared; local only.**PoC_slot_set****pending_verdicts**DiffwhoseVerdictis not yet final, keyed by(R_req, N_carry). Three reasons an entry sits here: (a)**Inconclusive**—I's endpoints (h_X,h_carry) are not yet strictly confirmed by the height-sync layer (resolution key: confirmation signal coveringI, see C6); (b)**Invalid**awaiting the round-elision / gossip deadline (C9/C10); (c)**provisional**within the seal window[h_carry, h_carry + W_seal]used by step (2) of the Verdict predicate — aMsgConfirmStartfor the sameinference_idmay still arrive and flip the verdict toInvalid(C2'). Note thatDiff[X]is always present by the timeDiff[N_carry]is ingested (becauseX ≤ R_req ≤ N_carryandDiffis append-only), soh_Xis always immediately computable — no "wait for witness" deferral exists. Each entry holds:N_carry,R_req, skipping hostH_i, raw signed host response (CPoCSkipResponseor refusal-outcomeCPoCProbeResponse), current tentative verdict (if any),provisional_untilmainnet height (when reason (c) applies), and the resolution key/deadline. Entries are removed on commit:Valid→ drop after the seal window expires;Invalid→ hand to finalization.ready-outcome carries never enter this buffer — they are recorded directly inready_at(below).**ready_at**host → (N_carry, h_carry, reset_height?)recording the latestCPoCProbeResponse(outcome = ready)for each host, from the most recentCarrySkipinDiffwithpayload_kind = probe_responseandoutcome = ready. Consumed by case C13 (developer withholding from a ready host). Cleared forH_iwhen V later observes either (a)Schedule(H_i, H) ∈ {active, prepare}strictly confirmed for someH > h_carry, or (b) a fresh non-readyCPoCProbeResponse/CPoCSkipResponseforH_icarried intoDiff.**withholding_alert**(D, H_i)flag set by V when the C13 violation predicate fires on localDiffobservations; cleared per C13 flow step 5. While set, V (if queued as a future executor forD) refuses to serveDuntil fairness is restored.Primitives
Names in
subnet/proto/subnet/v1/{tx,diff}.protounless marked (new). The verdict-predicate input set isMsgStartInference,MsgConfirmStart,MsgSkipProbe, andCarrySkip; the verdict-settlement input set isCPoCVote; the remaining messages are p2p carriers (CPoCSkipResponse,CPoCProbeResponse), delivery gossip (SkipEvidenceGossip), or final settlement (MsgTimeoutInference{…CPOC}).**MsgStartInference**D → devshardR_req:inference_id,prompt_hash,model,input_length,max_tokens,started_at. Path A only; this is the request the cPoC verdict anchors on.**MsgConfirmStart**H_i → devshardinference_id,executor_sig,confirmed_at. Absent whenH_iis skipping for cPoC. Presence alongside a matching Path-ACarrySkipfor the sameinference_idis a protocol violation: both messages carryH_i's signature on contradictory claims, and Verdict step (2) flips the verdict toInvalidagainstH_i(see § Cases → C2'). The mutual-exclusion check holds regardless of the order in which the two entries appear inDiff.**CPoCSkipResponse**H_i → Dinference_id,reference_nonce = R_req,reason ∈ {cpoc_active, cpoc_prepare}, optionalclaimed_height_h_i(informational; verdict ignores it), host signature under domaincPoCRefusalContent.**CPoCProbeResponse**H_i → Dprobe_nonce,reference_nonce = N_SP,outcome ∈ {cpoc_active, cpoc_prepare, ready}, optionalclaimed_height_h_i(informational), host signature under domaincPoCProbeResponseContent.readymeans H_i has exited cPoC and expects real inference requests.**CarrySkip**SubnetTxoneof)D → devshardCPoCSkipResponse(Path A) or aCPoCProbeResponse(Path B) — and places it at nonceN_carry:nonce = N_carry,referenced_nonce = R_req(orN_SP),payload_kind ∈ {skip_response, probe_response}, byteshost_response, developer signature under domainCarrySkipContent. The only verdict-bearing / scheduling-bearing cPoC artifact inDiff.**MsgSkipProbe**SubnetTxoneof)D → devshardprobe_nonce = N_SP,target_host_id,session/routing, no prompt payload. EntersDiffatN_SP. The host's response (CPoCProbeResponse) is p2p and echoed intoDiffvia a subsequentCarrySkipatN_carry > N_SP.CPoCVoteV → collectorValidlocal verdict. Fields:N_carry,referenced_nonce,target ∈ {host(H_i), carrier(D), developer(D)},verdict,reason_code,schedule_witness, signature under domaincPoCVoteContent. Collector: fortarget = host(H_i), developerDaggregates untilquorum_invalid(this release). For votes againstD(C3′, C13), aggregation belongs in the finalization round once self-finalization exists — notD. This release leaves that path unspecified (optimistic gap); see § Consensus / voting.MsgTimeoutInferencewithreason = TIMEOUT_REASON_CPOCCPoCVotequorum has decided a finalVerdict, the inference record is closed through the existing timeout path with the new reason. Carriesinference_id,repeated TimeoutVote votes. Verifiers do not need this to compute the verdict.SkipEvidenceGossipDiff(inference_id,N_carry, vote indexes). Delivery aid only — makes the sameCarrySkipvisible to lagging peers so they can compute their local verdict and emitCPoCVote. Does not itself contribute to the verdict or the vote bundle.End-to-end flow (happy path, host actually on cPoC)
Verdict predicate (normative shape)
Vcomputes**Verdict(skip_evidence) ∈ {Valid, Invalid, Inconclusive}**as:CarrySkipinDiffthat referencesR_req(see "First-carry rule" below):R_req ≤ N_carry. ACarrySkipcannot reference a request that has not yet enteredDiff. Failure →Invalidagainst the carrier (developer signature onCarrySkip), not againstH_i.X(per § Design principles → Nonce binding). LetSP_e = R_req mod N_slots,SP_v = v_slot,round(R_req) = ⌊R_req / N_slots⌋. Then:SP_v ≤ SP_e→X = round(R_req) · N_slots + SP_v(same round asR_req,X ≤ R_req).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 afterR_req; itsheight_atwould be observed afterR_reqand could not lower-boundH_skip. Stepping back one round gives the latest executor-of-Vnonce ≤R_req.X = R_req − ((SP_e − SP_v) mod N_slots).h_X := height_at[X]— V's local height when it ingestedDiff[X]. By constructionX ≤ R_req, soh_Xwas observed no later than the request itself.h_carry := height_at[N_carry] = H(V)at ingest ofDiff[N_carry].h_X ≤ h_carry(heights are monotonic at ingest), andh_carry ≤ H(V)_now(trivially — V is ingestingN_carryright now and stampsh_carryfromH(V)_now).**Diff[X]is always available whenDiff[N_carry]is being ingested.** By constructionX ≤ R_req, and causality (checked first in this step) requiresR_req ≤ N_carry, soX ≤ N_carry. BecauseDiffis append-only and ingested in order, everyDiff[k]withk ≤ N_carryis already present when V processesDiff[N_carry].Xdoes not identify a real prior executor-slot of V isround(R_req) = 0 ∧ SP_v > SP_e, where the closed form yieldsX < 0— V had no executor slot beforeR_reqin this session. V then falls back to the implicit session-start anchor (the lowest nonce V has ingested, typically 0) as the lower endpointh_X. This is a cold-start condition only; it does not recur once V has executed at least once.**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_Xandh_carry) — no cross-host height assumption required.First-carry rule. If the developer publishes multiple
CarrySkipentries for the sameR_req, only the earliestN_carryinDiffis 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 keepsIdeterministic across verifiers.Path B. For
MsgSkipProbe(case C7) the rule is identical, withR_req := N_SP(the probe nonce) andN_SP < N_carrystrictly.CarrySkipmay wrap either aCPoCSkipResponse(refusal) or aCPoCProbeResponse(status). If the carried outcome isready, 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 slotSP_v = v_slot = 2):R_reqSP_eN_carryround(R_req)Xh_Xh_carrySP_v = SP_e(V is executor)I = {500}(evaluateSchedule(H_i, 500)in step 3)SP_v = SP_eI = {500}SP_v = SP_eI = [500, 520]— step 3 seeks anyHin that interval on the cPoC scheduleSP_v < SP_e(same round)X = 11 − 1 = 10(same round asR_req);I = [500, 520]SP_v > SP_e(previous round)X = 9 − 3 = 6(round 1, not round 2);h_X = 498observed beforeR_req;I = [498, 520]SP_v > SP_e,round = 0h_XN_SP)SP_v = SP_eI = {500}(Path B;N_SP < N_carrystrictly)CarrySkipenvelope haspayload_kind = skip_response(Path A); skipped for Path B (payload_kind = probe_response, which referencesN_SPand has noinference_idto collide). Procedure:inference_id* := Diff[R_req].inference_id— read from the originalMsgStartInferenceentry (which must already be inDiffby causality, step 1).Difffor any entry satisfyingkind = MsgConfirmStart ∧ inference_id = inference_id* ∧ executor = H_i. Call the matching nonceN_confirmif found.N_confirm < N_carry→InvalidagainstH_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 contradictoryCPoCSkipResponsethatDlater carried. This is a cryptographically provable lie: both theMsgConfirmStart.executor_sigand the embeddedCPoCSkipResponsesignature areH_i's.N_confirm > N_carry(confirm appears after the carry) →InvalidagainstH_i,reason_code = double_claim_skip_then_confirm. Symmetric violation: the host refused the request, then later confirmed and ran the same inference.MsgConfirmStartforinference_id*may arrive after V has already ingestedN_carryand computed a verdict, the verdict from step (4) is provisional forW_sealmainnet blocks afterh_carry(W_sealis chain-parametrized — propose ≈2blocks, matchingtimeout_skip_gossip). During the seal window, if a contradictoryMsgConfirmStartlands, V re-runs the predicate and emits a supersedingCPoCVotekeyed on(N_carry, V_pubkey)(§ Consensus / voting); the collector keeps only the latest. AfterW_sealexpires the verdict is final and this step stops re-firing; any post-sealMsgConfirmStartis a settlement-layer issue, not a cPoC verdict flip. V tracks the seal window via aprovisional_until[N_carry] = h_carry + W_sealentry attached topending_verdicts.MsgConfirmStartforinference_idif aCarrySkipwhose embedded skip-response references the correspondingR_reqalready exists inDiff, and (ii) aCarrySkip(payload_kind = skip_response)referencingR_reqifMsgConfirmStart(inference_id = Diff[R_req].inference_id)already exists. BecauseDiffordering is already deterministic, this rejection is a pure function ofDiffand race-free. With this rule active the scan above catches only the seal-window race.H_i ∉ PoC_slot_set. Otherwise →Invalid(host hadPOC_SLOT = true, must not skip).I.∃ H ∈ I : Schedule(H_i, H) ∈ {prepare, active}→ candidateValid(subject to (5)). The host was legitimately on cPoC at some height V personally witnessed inI; that is sufficient.∀ H ∈ I : Schedule(H_i, H) == idle→ candidateInvalid(subject to (5)). The host claims cPoC refusal but is not on the schedule at any height inI.I(h_Xandh_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 asInconclusiveuntil height sync reports confirmation coveringI— then re-run step (4). This could be scheduled for future releasesCPoCSkipResponsemust be validly signed byH_iand referenceR_reqas it appears inDiff.Outputs feed Gossip minimization (below) and, for disputes, 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 nonceN_SP);N_carry= nonce at whichCarrySkipis appended toDiff; both paths haveR_req < N_carrystrictly.Rdenotes the executor round of sizeN_slots.C1 — Honest skip, honest developer (happy path)
Setup:
Schedule(H_i, H(V)) = active,H_i ∉ PoC_slot_set, dev behaves normally.Flow:
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, butH_irepliesCPoCSkipResponseto avoid work.Flow: Same as C1 up to the point the developer publishes
CarrySkip. Each hostVthen:Expected verdict:
Invalid(Schedule check fails on the height intervalI). TheInvalidoutcome is not attached to finalization by one party; it is the quorum ofCPoCVotes from hosts that observedDiff[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_isigns both aMsgConfirmStartand aCPoCSkipResponsefor the sameinference_id(directly or viaDcarrying the skip blob). The two messages are cryptographically incompatible:MsgConfirmStart.executor_sigcommitsH_ito running the inference, and the embeddedCPoCSkipResponsecommitsH_ito refusing it. Applicable only to Path A (payload_kind = skip_response); Path B has noinference_idon the carry and cannot trigger this case.Flow (confirm before carry):
Flow (skip carried first, confirm arrives inside the seal window):
Flow (confirm arrives after the seal window):
Expected verdict:
**InvalidagainstH_i** whenever both artifacts land inDiffwithin the seal window of each other. Settled via the standardCPoCVotequorum (§ Consensus / voting), with the vote bundle carryingreason_code ∈ {double_claim_confirm_then_skip, double_claim_skip_then_confirm}and pointers to bothDiffentries 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_ireturned a legitimateCPoCSkipResponseatR_reqduring its cPoC window (heightH_skip). Developer holds the blob for arbitrarily many rounds and later emitsCarrySkipatN_carry ≫ R_req.Flow:
Expected verdict:
**Valid**. The host's attestation is truthful for a height inI; 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
CarrySkipwithN_carry < R_req(references a request that has not yet enteredDiff).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 onCarrySkip), not againstH_i. This is a pure forgery check, independent of any height interval.C4 —
POC_SLOT = truehost returns skipSetup:
H_i ∈ PoC_slot_set(inference-exempt during others’ cPoC), yet repliesCPoCSkipResponse.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 sameInvalidand emitsCPoCVote; the collected bundle is the evidence handed to slashing (H_i).C5 — Skip during
preparewindowSetup:
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 confirmedH(V)for the nonce-window (out of scope for us — we only consume its signal).Flow: Verdict step (4) returns
Inconclusive.Expected action:
Vdoes not emit aCPoCVoteyet; it waits for the height layer to confirm. If confirmed Invalid,VemitsCPoCVote(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:
Dwants a cPoC status check fromH_iwithout submitting a prompt.Schedule(H_i, H) ∈ {active, prepare}at the height the probe is answered.Flow:
Expected verdict:
**Valid** (same predicate as Path A, applied withR_req := N_SP).C7' — Skip probe (Path B), ready outcome
Setup:
DprobesH_i.H_ihas finished its cPoC window and is inREADY_INFERENCE(Schedule(H_i, H) = idleat the answering height).Flow:
Expected verdict: not applicable. The carry is a scheduling receipt, not a skip attestation. It obliges the developer to resume routing real
MsgStartInferencetoH_iat subsequentH_i-slot nonces. Persistent deviation after this receipt triggers C13.C8 — No response at all (timeout)
Setup:
H_ireturns nothing (neither inference nor skip).Expected action: Out of scope of cPoC-skip verdict. Governed by
**USER_TIMEOUT** in FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md. cPoC protocol contributes no verdict in this case.C9 — Low-load vote collection (explicit gossip)
Setup: After
timeout_skip_gossipthe diff has not advanced one full round, so not everyVhas necessarily seen the carried skip and the vote collector (see § Consensus / voting) has not yet reachedquorum_invalid.Flow:
Expected verdict: whatever the vote quorum declares on the same
Diffevidence.SkipEvidenceGossipis a delivery aid only; it does not compute a verdict, it just makes the sameCarrySkipvisible so lagging peers can vote.C10 — High-load round elision
Setup: High request rate; the diff naturally advances past
R_req + N_slotswithintimeout_skip_gossip.Expected action: No
SkipEvidenceGossipemission needed; everyVhas the evidence by construction. EachVindependently computesVerdictand, if non-Valid, emitsCPoCVote. 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_invalidis reached, the collector assembles an evidence bundle consisting of: (i) the refs intoDiffforMsgStartInference/MsgSkipProbe,CarrySkip, and (for C13) theH_i-slot window; (ii) the set ofCPoCVotemessages achieving quorum; (iii) the relevant schedule inputs (PoC_slot_set,Scheduleat heights inI). This bundle is handed to 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:
Vhas stalePoC_slot_setor wrong epoch schedule (not the network majority view).Expected behavior:
Vis 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 penalizeH_iwhen only an outlierVdisagrees.C13 — Developer withholds work from a ready host (routing misbehavior)
Setup: Some host
H_ihas signalledready(either viaCPoCProbeResponse(outcome = ready)carried inDiffat some nonceN_ready, or becauseSchedule(H_i, H) = idleacross the lastW_readymainnet blocks that every verifier strictly confirms). The developer is nonetheless not routing real inference toH_i:executor(n) = H_i(i.e.n mod N_slots = i),Dkeeps sendingMsgSkipProbe(target = H_i)rather thanMsgStartInference, orDstops emitting messages atH_i-slot nonces altogether while continuing to send to other slots.Observation (at each
V).Vcounts, over a trailing window ofW_fairrounds ending at the current nonce:n_inf(H_i)=MsgStartInferenceentries withexecutor(n) = H_i,n_probe(H_i)=MsgSkipProbeentries targeted atH_i,H_iisready(perready_at[H_i]receipt orSchedule(H_i, H) = idlefor everyH ∈ [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,Dsent probes or leftH_i-slots empty at leastθ_fairtimes while sending fewer thanθ_min_infreal inferences toH_i, despiteH_ibeing ready. Exact values(W_fair, θ_fair, θ_min_inf)are chain-parametrized (TBD; see Open questions).Flow:
Expected verdict:
**Invalidagainst the developer**, not against any host. Evidence:ready_at[H_i]receipt + theH_i-slot window ofDiffshowing 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
Vfrom the sameDiffcontents, so every honest host reaches the same alert.Open parameters (deferred to Open questions):
W_fair,θ_fair,θ_min_infthresholds.ready_atreceipt decays after the host re-enters cPoC (presumably yes — onceSchedule(H_i, H) = activeagain, old receipts are cleared).RouteFairnessRefusaland whether it also lands inDiffas evidence for slashing D's stake.C14 — Low-load strategic delay (developer heartbeat mitigation)
Applicability: Only possible on low session load — specifically, when
Diffcontains no signed entries betweenR_reqandN_carrythat would otherwise tighten V's upper boundh_highonR_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 momentR_reqentersDiff). Immediately afterR_req, session traffic goes quiet:Dhas no other inferences to submit. A maliciousH_ithen waits strategically for its next scheduled cPoC window to open at some heighth > h_req, signsCPoCSkipResponse(R_req, active)during that later window, and relies onD's lateCarrySkiplanding far enough in the future that V's height intervalI = [h_X, h_carry]containsh. Under∃ H ∈ Isemantics (Verdict predicate, step 3) the carried refusal now passes, even though the host was idle ath_reqand therefore owed the developer real inference.Flow (attack, without mitigation):
Mitigation (developer heartbeat). When
Dhas an outstandingR_reqand no further inference to submit within the current round (R_req … R_req + N_slots),DSHOULD emit a lightweight heartbeat — aMsgSkipProbetargeted at the natural next slotexecutor(R_req + 1)— within ≈ 1 mainnet block ofR_req. The heartbeat carriesD's signedobserved_height ≈ h_req, and the host's respondingCPoCProbeResponse(carried back via a subsequentCarrySkip) carries the host's signedobserved_heightas well. Both stamps land inDiffat nonces> R_req, providing a tight upper boundh_highonR_req's true height.Cadence — one heartbeat, one round, only while idle.
Demits the heartbeat once within the round ofR_req. A single stamped entry is sufficient to tightenh_high; additional heartbeats add no verdict strength.R_req. Once the session advances past nonceR_req + N_slots(one full executor round), the band forR_reqis already bounded from above by any signed entry in that window.DMUST NOT continue emitting heartbeats after the round closes — further ones no longer improve the verdict forR_req.Dwould otherwise leaveDiffquiet. IfDhas realMsgStartInferencetraffic queued (any nonce in[R_req + 1, R_req + N_slots]), those entries already provideh_highvia their ownobserved_heightstamps — no heartbeat is emitted.Flow (mitigated):
Interaction with other cases.
H_iitself andH_irespondsready, the response contradicts its own laterCPoCSkipResponse(R_req, active)— a double-claim analogous to theMsgConfirmStartvs.CPoCSkipResponsemutual-exclusion rule. Verdict isInvalidagainstH_ion sight, without needing the band to resolve.H_{i+1}(the natural case sinceR_req + 1's executor isH_{i+1}), C13's withholding detector MUST exempt heartbeat probes emitted while anR_reqawaits verdict — the probe is height-sync machinery, not a sustained routing pattern. See Open questions.Dfails 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 lazyDexposes itself to being lied to. This aligns incentives: heartbeating protectsD's own payment for real work.Expected verdict: With the heartbeat in place, the same
CPoCSkipResponsethat would have strategically passed under a wide band now fails step 3 and is settledInvalidvia the standardCPoCVotequorum (§ Consensus / voting). Without the heartbeat on a low-load session, the protocol's verdict fidelity degrades gracefully — the verdict is whatever∃ H ∈ Ireturns on the wide band — and settlement-layer penalties on host withholding remain the only recourse.Open parameters (deferred to Open questions):
R_reqand the heartbeat (≈ 1 mainnet block is a suggestion; could be tighter or looser).MsgSkipProbeor a dedicated lightweight message without a response expectation.MsgSkipProbeis reused here because it already carries anobserved_heightand rides existing Diff wire formats, but a response-free variant is cheaper.Consensus / voting
Every verifier
Vcomputes the Verdict predicate (§ Data flow) independently against its local view ofDiffandH(V). WhenVerdict ∈ {Invalid, Inconclusive-pending-confirmation}(or a C13 developer-withholding alert fires),Vsigns and emits aCPoCVotefor thatN_carry. Fortarget = host(H_i), votes are addressed toDas collector (this release). FortargetnamingD(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)N_carryCarrySkipthis vote refers to (or, for C13, the earliestDiffreference in the evidence window).referenced_nonceR_reqorN_SP, copied from the carry; lets the collector filter duplicates.targethost(H_i)for C2/C4/C6,carrier(D)for C3',developer(D)for C13.verdictInvalid(most common).Validvotes are implicit — honest verifiers simply don't emit a vote — so noValidvoting channel is required.reason_codeschedule_fail,role_fail,causality_fail,double_claim_confirm_then_skip,double_claim_skip_then_confirm,withholding,height_confirmed_invalid, …).schedule_witness(H*, Schedule(H_i, H*))for the height inIthe verifier consulted, so the bundle is self-contained for slashing.signaturecPoCVoteContent(binds all fields above).A single
CPoCVoteis cheap; the flood size is bounded because only verifiers with a non-Validlocal verdict emit one, and every one is a pointer into existingDiffentries.Collector: this release vs. self-finalization (including votes against
D)Host-fault cases (
target = host(H_i)— C2, C2′, C4, C6, etc.). The developerDis the vote collector for this release:Dalready owns theCarrySkipenvelope and knows whichN_carrythe vote refers to.Dis the economically interested party when a malicious host meansDdid not get served.Collection procedure:
Vwith a non-Validverdict sendsCPoCVotetoDvia p2p (optionally piggy-backed on the same channel that carriesSkipEvidenceGossip).Daggregates distinct signatures until|votes(Invalid)| ≥ quorum_invalid.Dattaches the bundle to finalization per FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md. The vote bundle is the input to slashing.Developer-target cases (
targetnamesD— C3′ forged carry, C13 withholding).Dcannot be the trusted aggregator of votes that would slash or disputeD. Normative intent: once self-finalization is implemented,CPoCVotes for these targets MUST be collected and aggregated in the finalization round (the same developer-independent path as other settlement), not byD.This release — optimistic gap. The protocol does not specify a collector for developer-target votes. We assume
Dbehaves honestly when forwarding or aggregating evidence in practice, or that C3′/C13Invalidoutcomes are out-of-band rare; maliciousDcensoring or withholdingCPoCVotes against itself is a known uncovered negative case, scheduled for closure when self-finalization lands. Verifiers still emitCPoCVotewithtarget = developer(D)/carrier(D)as specified; only the trusted aggregation path is deferred.Future release (self-finalization). When the finalization round aggregates
CPoCVotewithout relying onD:Vstill emitsCPoCVoteon the standard channel; wire format unchanged.Dfor any target.Dstops 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 and the mainnet slashing spec. This doc only guarantees:Vreaches the same verdict from the sameDiff+ strictly-confirmed height slice (by construction of the Verdict predicate).CPoCVoteincludes theschedule_witnessand is auditable at finalization time (a dishonest vote is itself slashable).Open questions (for formalization)
**PoC_slot_setprovenance:** set at escrow init (immutable) vs queried post-init and cached. Different failure modes.**preparepolicy:** is skip allowed whileSchedule = prepare(treat likeactive) or forbidden (treat likeidle)? Chain-spec flagskip_allowed_during_prepare.cPoCRefusalContent(host signature onCPoCSkipResponse, bindsinference_id+reference_nonce+ reason),cPoCProbeResponseContent(host signature onCPoCProbeResponse, bindsprobe_nonce+reference_nonce+ outcome),CarrySkipContent(developer signature onCarrySkip, bindsN_carry+referenced_nonce+payload_kind+host_responsebytes), and the signing input forMsgSkipProbe(bindsprobe_nonce = N_SP+target_host_id).Diff-refs, signatures, schedule-witness); shared with FINALIZATION_COLLECTOR_PROTOCOL_PROPOSAL.md.(W_fair, θ_fair, θ_min_inf)for the developer-withholding predicate: how manyH_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 afterreadybefore resuming inference) does not trigger alerts.**ready_atlifecycle.** When exactly does areadyreceipt forH_iexpire? Candidates: (a) on the first strictly-confirmedSchedule(H_i, H) ∈ {active, prepare}after the receipt; (b) on any subsequent non-readyCPoCProbeResponse/CPoCSkipResponseforH_icarried inDiff; (c) a hard TTL in mainnet heights. Likely all three with(a) ∨ (b) ∨ (c).**RouteFairnessRefusalsurface.** Is this purely a p2p refusal signal between hosts, or must it also land inDiffas a signed artefact so mainnet can slashD? If the latter, it becomes anotherSubnetTxvariant and needs its own signing domain.MsgSkipProbe→ p2p response →CarrySkiproundtrip be eliminated by lettingDplace a D-signed unilateral-skip marker (e.g.MsgCPoCSkipMarker(nonce, target_host = H_i, basis = {N_prev_carry, h_prev})) atH_i's slot nonce and routing the realMsgStartInferenceto the next slot? Requires (i) wire format for the marker and its signing domain; (ii) a freshness rule keyed to a priorCarrySkipforH_i— the marker is valid only while the schedule-implied cPoC window referenced byN_prev_carryhas not expired at V's current height; (iii) a per-evidence cap on consecutive unilateral skips so a single oldCarrySkipcan't authorize indefinite skipping; (iv) reconciling withready_at[H_i]and the C13 detector — areadyreceipt invalidates outstanding marker authority immediately. Explicitly out of scope for the current release.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 asValid" are chain-parametrized and deferred to the finalization / slashing spec.CPoCVotewithout relying onD, we need: (i) a deterministic boundary condition that triggers vote aggregation (block height, session sealing, etc.); (ii) explicit ingestion ofCPoCVotewithtarget = developer(D)/carrier(D)(C3′, C13) so aggregation is not left toD; (iii) handling for late-arriving votes across the boundary; (iv) a migration story so older nodes that still send host-fault votes toDcompose with the new collector. The wire format ofCPoCVoteitself should not need to change — only the aggregation destination. This closes the optimistic gap documented in § Consensus / voting (maliciousDcensoring votes against itself). Explicitly out of scope for the current release.R_reqand the heartbeat (≈ 1 mainnet block proposed; tune against network latency). (ii) Whether the heartbeat reusesMsgSkipProbeor justifies a dedicated response-free lightweightSubnetTxvariant (which would bind onlyD's signedobserved_heightand incur no p2p roundtrip). (iii) Carve-out rule in C13's withholding tally for probes emitted while anR_reqawaits verdict, so a legitimate heartbeat doesn't count as withholding fromH_{i+1}. (iv) Whetherobserved_heightfields are strictly required onMsgStartInference,MsgConfirmStart,MsgSkipProbe, andCarrySkipfor verifier determinism, or whether V's ownheight_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.W_seal. Default proposed at ≈ 2 mainnet blocks (matchingtimeout_skip_gossip). Needs to be tuned against (i) realisticMsgConfirmStartarrival latency after aCarrySkip, (ii) how long verifiers can reasonably bufferpending_verdictsentries in theprovisionalstate, (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 extendingW_sealor allowing a bounded number of post-seal flips recorded as "late evidence" rather than verdict changes.MsgConfirmStartwhenCarrySkip(payload_kind = skip_response)for the sameinference_idalready exists inDiff(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
H(V)as a black-box oracle.Invalidverdicts, decides inclusion in finalization bundles.Beta Was this translation helpful? Give feedback.
All reactions