Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 72 additions & 55 deletions MINER_VALIDATOR.md
Original file line number Diff line number Diff line change
@@ -1,77 +1,94 @@
# What a miner and a validator run on SN39

SN39 (Cathedral) rewards **two lanes of verified work**, composed into one weight vector
each tempo — **70% Intel-TDX compute** / **30% CyberGym vulnerability solving**, with any
unfilled share burned. This page is the whole mental model. The deeper contracts are in
[VALIDATOR.md](VALIDATOR.md), [deploy/MECHANISM_ROUTER_CONTRACT.md](deploy/MECHANISM_ROUTER_CONTRACT.md),
and the reward-path docs.
This page describes what the shipped validator and publisher **actually do**.
If a sentence here disagrees with a flag default, the flag default is the
product. See [docs/CYBERGYM_LANE.md](docs/CYBERGYM_LANE.md) for the CyberGym
switches and the v3 gate.

## What is live today

SN39 currently pays **one lane**: attested Intel TDX compute, composed as
`validated_supply_v1` (v2 vector: 90% TDX / 10% burn). CyberGym is implemented
in the publisher, and it is **switched off** in what we ship. Four flags are
all off. No CyberGym weight and no CyberGym money move today.

That is intentional until cathedral-distill refuses public-catalog `task_id`s
(`arvo:<n>`, `oss-fuzz:<n>`). Do not set `CATHEDRAL_ALLOCATION_CONTRACT=v3`
before that fix is deployed. Flipping v3 first is the one non-survivable
move: a miner who sees those ids can pull `n132/arvo:<n>-vul` and read
`/tmp/poc`.

## A miner

One neuron, one lane at a time:
One neuron, one live lane:

```
register a hotkey on SN39 (btcli subnet register --netuid 39 …)
└─ do verified work in a lane:
• compute : run the approved Intel-TDX workload → a Cathedral attestation receipt
• cybergym : solve the dispatched sealed PoC → a proof-of-crash + trace
└─ submit it: an on-chain COMMIT of H(work_hash ‖ your_hotkey) + an off-chain pointer
└─ the artifact is graded after a short reveal delay
register a hotkey on SN39
└─ run the approved Intel TDX workload
└─ Cathedral issues a customer receipt over that work
└─ the producer exports a signed score-class report + public evidence
```

- The receipt / PoC lives **off-chain**; only the hotkey-bound hash + pointer go on-chain,
so a copier who points at your artifact commits a hash that will not validate under their
hotkey.
- You earn **proportional to verified work** (not a fixed slot): more verified solves / more
attested compute → more of your lane's share.
CyberGym solving is not a paying lane on the shipped defaults. Miners for
that lane come after the task-id fix and a coordinated v3 cutover.

## A validator

One neuron, `cathedral-validator serve`. Each tempo it does exactly this:
One neuron, `cathedral-validator serve`. Each tempo:

```
1. COMPUTE lane — for each miner, fetch its Intel-TDX receipt and DCAP-verify it
*itself*, then score. (Publicly verifiable → every validator agrees.)
2. CYBERGYM lane — ingest the corpus holder's signed score report and independently
verify its attestation receipt (the spot-check), then score.
3. COMPOSE — 70% compute / 30% cybergym; a lane that fails to verify BURNS its
share rather than handing it to the other lane.
4. SET WEIGHTS — one path: smooth, normalize, apply the chain limits + commit-reveal,
zero any replaced hotkey, and confirm the extrinsic landed.
1. COMPUTE lane — ingest Cathedral's signed compute feed (not a per-validator
Intel DCAP replay of every miner). The public evidence
chain is independently verifiable. Validator-self-scored
compute is still building (see Status).
2. CYBERGYM lane — only if v3 is selected AND the mechanism is enabled.
Ingest the corpus holder's HMAC-authenticated score report.
Optional spot-check: verify Cathedral's Ed25519 signature
on one carried receipt. Default: record failure, pay anyway.
3. COMPOSE — v2: 90% compute / 10% burn.
v3 (not shipped): 70% compute / 30% CyberGym; an
unfilled CyberGym share burns. A v3 compose that cannot
build the CyberGym lane fails the whole vector.
4. SET WEIGHTS — one path: smooth, normalize, chain limits, commit-reveal.
```

It **verifies before it trusts**: it never relays a score it has not checked. Run it in
**shadow** first (`--dry-run`, composes but writes nothing), then broadcast for real from
the staged install (`deploy/sn39`).
### What "attestation" means on this path

## Why the two lanes are scored differently
- Offline verifier success on a Cathedral receipt proves Cathedral signed
those exact assertions with the pinned Ed25519 key.
- It does **not** independently replay vendor evidence.
- It does **not** prove AMD SEV host attestation.
- It is **not** "every validator independently DCAP-verifies CyberGym."
That sentence was wrong. The code checks Cathedral's signature.

| Lane | Scored by | Because |
|---|---|---|
| **Compute / TDX** | **each validator, independently** | an Intel-TDX (DCAP) receipt is publicly verifiable — no secret needed, so it is fully decentralized. |
| **CyberGym** | the **corpus holder**, centrally, then relayed | the vulnerability holdout is a **sealed private corpus** — if every validator held it, so could miners, and the "solve an unseen bug" mechanism collapses. It stays central, but **provably honest**: the signed report carries one Intel-signed attestation receipt that every validator verifies binds to *this epoch's chain-named miner*. |
`CATHEDRAL_CYBERGYM_REQUIRE_ATTESTATION_RECEIPT` is off by default. When off,
a missing or invalid carried receipt is recorded and the lane still pays.
Once an audience has ingested a receipt-bearing report, a later report
without one is refused by the ingest ratchet even while this flag stays
off. Real Intel DCAP quote verification is separate work.

That single asymmetry — decentralize what is verifiable, attest what cannot be — is the
design. A validator on this network is never a rubber stamp: the compute lane it computes,
and the CyberGym lane it *proves*.
## Why the two lanes are scored differently (design, not today's defaults)

## Getting started

- **Run a validator:** [deploy/sn39/docker/](deploy/sn39/docker/) (Docker, 3 commands) or
the native quickstart in the [README](README.md#quickstart). Register + stake a hotkey
first (an unstaked validator's weights are ignored by consensus).
- **Run a miner:** register a hotkey, pick a lane, submit. (Miner tooling: `cathedral-miner`.)

## Status (what's shipped vs. building)
| Lane | Who scores it today | Why |
|---|---|---|
| **Compute / TDX** | Cathedral producer, relayed. Each validator checks the signed vector + public evidence, not a local DCAP loop over miners. | An Intel TDX receipt is publicly verifiable; the live path still uses the signed feed. |
| **CyberGym** | Corpus holder, HMAC-relayed, then optional Cathedral-signature spot-check | The holdout is a sealed private corpus. If every validator held it, so could miners. |

This page is the **launch design**. Some of it ships today; some is the refactor that gets us
there (tracked in the launch refactor plan):
## Getting started

- **Shipped:** the validator that composes lanes + sets weights (`scaffold/publisher/mechanism_router.py`,
`scaffold/validator_thin.py`); the CyberGym lane ingest + tournament + **attestation spot-check**
(`mechanism_cybergym_adapter.py`, `cybergym_attestation.py`); onboarding (`deploy/sn39`).
- **Building:** moving the **compute lane from relayed to validator-self-scored** (DCAP-verified in
the validator), and the **miner commit-reveal** submission path. Until then the compute lane is
relayed from Cathedral's signed feed, and the miner path is the compute/`cathedral-miner` attestation
flow. The end state is one validator + two lane modules + one miner — no separate relay.
</content>
- **Run a validator:** [deploy/sn39/docker/](deploy/sn39/docker/) or the native
quickstart in the [README](README.md#quickstart). Register + stake a hotkey
first.
- **Run a miner:** register a hotkey and run the compute/`cathedral-miner`
attestation flow.

## Status (shipped vs building)

- **Shipped:** compose + set weights; CyberGym ingest + tournament + Cathedral
Ed25519 spot-check (default advisory, mechanism default off); SN39 onboarding.
- **Shipped defaults:** `require_policy = validated_supply_v1`;
`CATHEDRAL_ALLOCATION_CONTRACT` unset/v2; `CATHEDRAL_CYBERGYM_MECHANISM_ENABLED`
false; `CATHEDRAL_CYBERGYM_WEIGHT_FRACTION` 0.0.
- **Building:** validator-self-scored compute (local DCAP of miner receipts);
miner commit-reveal; real Intel DCAP verification of CyberGym receipts;
distill public-catalog `task_id` refusal (blocks any v3 flip).
5 changes: 2 additions & 3 deletions config/validator-selfcompose-sn39.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,8 @@ url = "http://127.0.0.1:8010"
[weight_policy]
public_key_hex = "10890a66aa752479cb3b634f366d7bd27c374324d83f88d2d6b69ab066f25e26" # pragma: allowlist secret
key_id = "cathedral-weight-policy"
# v2 (90% Intel TDX / 10% burn) — the byte-identical default. Flipped to
# validated_supply_v3 only in the coordinated Phase 5 cutover, paired with the
# publisher-side v3 flip, and only after CyberGym is confirmed healthy.
# v2 (90% Intel TDX / 10% burn). Do not flip to validated_supply_v3 until
# cathedral-distill refuses public-catalog task_ids. See docs/CYBERGYM_LANE.md.
require_policy = "validated_supply_v1"
state_file = "/var/lib/cathedral-validator/thin-state.json"

Expand Down
24 changes: 21 additions & 3 deletions deploy/publisher/cathedral-publisher.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,28 @@ CATHEDRAL_DB_PATH=/var/lib/cathedral-publisher/publisher.db

# --- allocation contract (v2 default) ---------------------------------------
# The consolidated validator's config/validator-selfcompose-sn39.toml pins the
# validator to require_policy = "validated_supply_v1" (v2). Keep this side on v2
# too until the paired Phase 5 flip. See deploy/publisher/README.md.
# CATHEDRAL_ALLOCATION_CONTRACT=v3
# validator to require_policy = "validated_supply_v1" (v2). Keep this side on v2.
#
# HARD RULE: do not set CATHEDRAL_ALLOCATION_CONTRACT=v3 until cathedral-distill
# refuses public-catalog task_ids (arvo:<n>, oss-fuzz:<n>). Those ids name the
# public n132/arvo image. v3 is the only path that puts weight through that
# hole. See docs/CYBERGYM_LANE.md.
#
# v2 (what this file ships): 10% fixed burn. Leave the next block commented.
# CATHEDRAL_WEIGHT_POLICY_FORCED_BURN_PERCENTAGE_V2=10
#
# v3-only stanza. Copy as a set. A partial flip fails the whole vector.
# CATHEDRAL_VALIDATED_SUPPLY_ENABLED=1
# CATHEDRAL_ALLOCATION_CONTRACT=v3
# CATHEDRAL_CYBERGYM_MECHANISM_ENABLED=1
# CATHEDRAL_CYBERGYM_WEIGHT_FRACTION=0.30
# CATHEDRAL_WEIGHT_POLICY_FORCED_BURN_PERCENTAGE_V2=0
# CATHEDRAL_WEIGHT_POLICY_BURN_UID=
# CATHEDRAL_WEIGHT_POLICY_BURN_HOTKEY=<burn hotkey>
# CATHEDRAL_EXTERNAL_SCORES_ENABLED=1
# CATHEDRAL_EXTERNAL_SCORES_SOURCE=cathedral_confidential_tdx
# CATHEDRAL_EXTERNAL_SCORES_MODE=confidential_primary
# CATHEDRAL_EXTERNAL_SCORES_PRIMARY_CONFIRM=true

# --- serving ----------------------------------------------------------------
# This role binds the local address the validator's [publisher] url points at
Expand Down
9 changes: 3 additions & 6 deletions deploy/sn39/cathedral-validator-hardening.conf
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,13 @@
# against that instruction. Restart=always and a poison exit are a contradiction.
# Exit 3 is reserved for "this will not succeed by trying again".
#
# StartLimit: even a retryable failure should stop hammering. Five failures in ten
# minutes means something is wrong that another restart will not fix.
# Do not put StartLimitBurst in this drop-in. Pending-receipt recovery exits 1
# until archive proof is available. A generic start cap disables that retry
# and leaves the fenced signed attempt unrecovered.

[Service]
MemoryMax=2G
MemoryHigh=1500M
Restart=on-failure
RestartSec=15s
RestartPreventExitStatus=3

[Unit]
StartLimitIntervalSec=600
StartLimitBurst=5
4 changes: 4 additions & 0 deletions deploy/sn39/cathedral-validator-sn39-relay.service
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ ExecStartPre=/bin/sh -c '! systemctl is-active --quiet cathedral-validator-sn39.
ExecStart=/usr/bin/python3.12 -I -E -s /usr/local/libexec/cathedral-sn39-release continuous
Restart=on-failure
RestartSec=15s
# Same stay-down as the origin unit. A post-signed contradiction exits 3
# and must not be restarted. Do not add StartLimitBurst here: pending
# receipt recovery exits 1 and must keep retrying.
RestartPreventExitStatus=3
TimeoutStopSec=30s
StateDirectory=cathedral-validator
StateDirectoryMode=0700
Expand Down
6 changes: 6 additions & 0 deletions deploy/sn39/cathedral-validator-sn39.service
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ ExecStartPre=/bin/sh -c '! systemctl is-active --quiet cathedral-confidential-va
ExecStart=/usr/bin/python3.12 -I -E -s /usr/local/libexec/cathedral-sn39-release continuous
Restart=on-failure
RestartSec=15s
# #114: contradiction exits 3 ("will not succeed by trying again"). Without
# this, Restart=on-failure restarted the writer 66 times against its own
# stay-stopped remediation. See cathedral-validator-hardening.conf.
RestartPreventExitStatus=3
MemoryMax=2G
MemoryHigh=1500M
TimeoutStopSec=30s
StateDirectory=cathedral-validator
StateDirectoryMode=0700
Expand Down
4 changes: 3 additions & 1 deletion deploy/sn39/docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ services:
dockerfile: deploy/sn39/docker/Dockerfile
env_file: .env
command: ["validator"]
restart: unless-stopped
# on-failure restarts exit 1 (pending-receipt retry). Exit 3 is translated
# to a stay-stopped sleep in the entrypoint so this policy never loops it.
restart: on-failure
volumes:
# Wallet is READ-ONLY: the validator only reads the hotkey to sign, never writes it.
- ${HOST_WALLET_PATH:-~/.bittensor/wallets}:/root/.bittensor/wallets:ro
Expand Down
15 changes: 14 additions & 1 deletion deploy/sn39/docker/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ case "$ROLE" in
;;

validator)
if [ -f /state/stay-stopped ]; then
echo ">> stay-stopped: previous post-signed contradiction. Not starting." >&2
exec sleep infinity
fi
if ! _detect; then
echo ">> No valid validator candidate for this wallet — see guidance above. Not starting." >&2
exit 2
Expand All @@ -50,11 +54,20 @@ case "$ROLE" in
FLAG=--dry-run
echo ">> SHADOW (default): reads chain, composes, writes NOTHING (dry-run)." >&2
fi
exec cathedral-validator serve --config "$CFG" \
set +e
cathedral-validator serve --config "$CFG" \
--runtime-root /state \
--state-file /state/thin-state.json \
--jsonl /state/validator-events.jsonl \
"$FLAG" "$@"
status=$?
set -e
if [ "$status" -eq 3 ]; then
echo ">> stay-stopped: post-signed contradiction (exit 3)." >&2
date -u +"stay-stopped %Y-%m-%dT%H:%M:%SZ" > /state/stay-stopped
exec sleep infinity
fi
exit "$status"
;;

shell) exec /bin/bash "$@" ;;
Expand Down
Loading
Loading