fix(op1): lock canonical data by manifest_hash, not an absolute-path blocklist - #88
fix(op1): lock canonical data by manifest_hash, not an absolute-path blocklist#88atosonik wants to merge 1 commit into
Conversation
…blocklist
Since #655 the proof runner injects an absolute realpath for --manifest and
--data-base-dir (proof/runner.py:444,449), recipe train.py main() writes it into
cfg, and final_state["config"] records it via asdict(cfg). op1's
check_canonical_data_source rejected ANY absolute path -> it fatally rejected
every honest bundle (even the validator's own /app/data re-run), while still not
catching a redirect to a same-named manifest
("/home/root/diony/.../data/data_manifest.json").
Enforce the data-lock by CONTENT instead: final_state.manifest_hash (bound into
bundle_hash, tamper-evident) must equal the validator's canonical training
manifest hash -- exactly what data/manifest.py manifest_hash() already promises
("so validators can verify which manifest the miner trained against"). When the
canonical manifest can't be recomputed, fall back to the original strict path
guard. Existing path-guard tests still pass; adds content-lock tests.
|
Thanks for catching the #85/#86 contradiction — the diagnosis is exactly right (the #655 runner pin injects an absolute path into hash-bound final_state.config, and the allowlist then rejects it). Three issues with the manifest_hash content-lock, verified against the live netuid-40 validator:
Since resolution is already runner-pinned (proof/runner.py chooses --data-base-dir, overriding miner config), gate-4 really just needs to stop false-rejecting the runner own absolute path. The minimal fix is to relax it (accept the runner-pinned absolute path, keep rejecting ~ and ..), not add a content-lock that is inert + risky. The durable defense is pre-crown re-derivation (re-run a slice of training on canonical data, check the loss trajectory reproduces), which we are building now. Heads-up: this also overlaps validator/integrity.py with the op1-hardening in #89 (timing gate + require-final_state + fraud-checkpoint blocklist), so they will need reconciling. |
The bug: op1 rejects every honest bundle
After the canonical bump (#86/#655), op1's
check_canonical_data_sourceis self-contradictory:proof/runner.py:444,449injects an absolute realpath for--manifestand--data-base-dir(intentional, to pin shard resolution).train.pymain()writes those intocfg;final_state["config"]records them viaasdict(cfg).check_canonical_data_sourcerejects any absolute path (_NONCANONICAL_PATH_RE = ^\s*(?:~|\.\.|/)).So every honest run trips it (
validator.py→ fatal op1 reject) — including the validator's own/app/datare-run. Reproduced: running the runner's exacttrain_cmdonc813831recordsconfig.manifest_path=/.../data/data_manifest.json→check_canonical_data_source→False.It also never caught the original threat:
"/home/root/diony/recipe/data/data_manifest.json"ends in the canonical suffix yet points at miner data — a path string can't tell canonical from contaminated.The fix: lock by content, not path
Enforce the data-lock by content —
final_state.manifest_hash(bound intobundle_hash, tamper-evident) must equal the validator's canonical training-manifest hash. This is exactly whatdata/manifest.py::manifest_hash()already promises: "the value extended into the proof-test attestation user_data so validators can verify which manifest the miner trained against." The validator just wasn't checking it.Tests
tests/test_integrity_trained.py: all existing path-guard tests still pass; adds 3 content-lock cases (honest absolute path accepted, redirect-by-hash rejected, fallback guard). 26 passed.Single file changed (
validator/integrity.py) + tests. No change to measured paths (validator/ is not incontainer_measurement).