Skip to content

fix(validator): refuse owner-burn and unsealed LKG submit - #208

Draft
echobt wants to merge 3 commits into
mainfrom
cursor/refuse-owner-burn-submit-78b0
Draft

fix(validator): refuse owner-burn and unsealed LKG submit#208
echobt wants to merge 3 commits into
mainfrom
cursor/refuse-owner-burn-submit-78b0

Conversation

@echobt

@echobt echobt commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Live GET /v1/weights/latest can be a sealed burn-uid0.v1 vector uids:[0] weights:[1.0]. UID 0 is the subnet owner+validator hotkey, so Yuma pays the owner — that is not a burn.

This PR only stops paying the owner. It does not register a discarded-key burn neuron, pin burn_uid, or bump algorithm_version / burn-eligible-v1 (follow-up). Frozen BUNDLE_SPEC §6 vectors / protocol_version / algorithm_version are unchanged.

  • Unsealed latest is not a submit path. apply_unsealed_latest logs validator_latest_unsealed and returns None. It does not load LKG, compare, or submit. lkg.save still runs on sealed Match (disk ok; submit forbidden while latest.sealed=false).
  • Sealed Match submit only if latest.sealed==true, outcome is Match, and the vector is not is_burn_to_registered_owner: one UID holds all non-zero mass and that UID’s hotkey is SubnetOwnerHotkey or holds validator_permit at the seal metagraph. Covers sealed zero-miner {0: 1.0} to owner. On hit: no submit, log validator_submit_skipped_owner_burn, no new dissent code.
  • HTTP fallback unchanged: build_burn_fallback still serves uid0=1.0 sealed:false so /latest never 404s. Validators must not Match/submit it; submitting burn-uid0.v1 pays the owner.
  • Seal-lag warn text is explicit: live sources still design+prism sealed 2026-08-22; Relearn must replace those sources once ready. Do not treat design/prism as the target live set.
  • BUNDLE_SPEC §9 MAY-LKG is revoked. Frozen §6 / algorithm_version / protocol_version are not bumped.
  • Lockfile: wnaf 0.14.0 → 0.14.1 (cargo update -p wnaf). Same cargo-deny yanked-crate fix as feat(proof): Proof challenge + dynamic topics + RLM judge digest pin #209. No product-scope change.

Greptile

Every PR is reviewed by Greptile before merge. Config: .greptile/.

  • Greptile has reviewed this PR; findings are fixed or answered
  • If the bot was silent, I commented @greptileai review

Test plan

  • cargo test -p validator -p chain -p chain-live -p weights-api -p challenge-common
  • cargo fmt --all -- --check
  • Clippy on touched crates (-D warnings; maybe_submit_match split so it stays under too_many_lines)
  • cargo run -p xtask -- spec-check / loc-cap / external-docs-check / design-check / consensus-lint
  • cargo deny check (local, after wnaf 0.14.1)

Risk

Validators that previously submitted LKG on gateway burn-fallback, or submitted a sealed uid0=100% owner vector, will stop submitting until a sealed non-owner vector exists. Chain may remain at owner 100% until a real miner vector is sealed (or a later eligible-sink PR). HTTP /latest still never 404s.

Naming

I did not rename BASE_* environment variables, deployed host paths
(/opt/base, /run/base, …), GHCR baseintelligence/base package names, or
base-*-v1 cryptographic domain tags, unless this PR’s purpose is a coordinated
cutover documented in docs/NAMING.md.

Open in Web Open in Cursor 

cursoragent and others added 2 commits September 3, 2026 21:02
UID 0 is the owner+validator hotkey, so a sealed uid0=100% vector is a
payout, not a burn. Skip Match submit when one UID holds all mass and is
SubnetOwnerHotkey or validator_permit. Unsealed latest is not a submit
path: do not load or submit LKG. HTTP burn-uid0.v1 fallback is unchanged.

Co-authored-by: Mathis <echobt@users.noreply.github.com>
maybe_submit_match exceeded clippy::too_many_lines; record_submit_outcome
keeps the Match submit path under the 100-line cap.

Co-authored-by: Mathis <echobt@users.noreply.github.com>
@echobt

echobt commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

@greptileai review

@greptile-apps

greptile-apps Bot commented Sep 3, 2026

Copy link
Copy Markdown

Greptile Summary

This change adds sealed-vector safeguards so validators avoid submitting unsealed results and pure payouts to subnet owners or validator-permit UIDs. Focused execution reproduced two paths that bypass those safeguards: unreadable permit storage is treated as an empty permit map, and an unreadable seal-time metagraph is replaced with mutable tip state. Both cases can submit a sealed pure payout that should be skipped.

Merge safety: do not merge until permit-state uncertainty and unavailable historical seal snapshots both fail closed.

Confidence Score: 3/5

Unsafe to merge because two independently reproduced payout-guard bypasses can submit sealed vectors that should be rejected.

Focused Rust tests exercised both failure paths through the submission flow: malformed permit data produced an empty permit map and a recorded submission, while an unavailable historical snapshot changed a skipped payout into a submitted payout.

Files Needing Attention: crates/chain-live/src/lib.rs must preserve permit-read uncertainty, and crates/validator/src/epoch_loop.rs must not replace an explicitly requested seal snapshot with current-tip state.

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex produced initial P1 finding proofs and uploaded review-authored test sources and outputs for the chain-live ValidatorPermit and validator-payout submission tests.
  • T-Rex captured and linked follow-on test artifacts showing historical seal snapshot control run and unavailable seal snapshot fallback run.
  • T-Rex documented contract-validation results, showing fetch_validator_permit maps malformed RPC storage through decode_vec_bool to an empty vector and is_burn_to_registered_owner handles missing UID 1 as expected; the focused submission test passed.
  • T-Rex posted two additional P1 findings and corresponding review comments, with proofs 2 and 4.
  • T-Rex completed further validation confirming the fallback uses the mutable tip classification and that a submission is permitted under specified conditions; two cargo test commands exited with code 0.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (1)

  1. General comment

    P1 Malformed, missing, or unreadable ValidatorPermit storage bypasses the pure validator-payout guard

    • Bug
      • A sealed Match allocating all weight to a validator-permit hotkey can reach submit_intent when ValidatorPermit(netuid) cannot be decoded/read or is absent. The live client converts those cases to an empty permit vector, and the submission guard treats the missing UID entry as false. The focused test observed a malformed SCALE response produce validator_permit == []; the subsequent sealed UID-1-only Match recorded one submission and marked the epoch submitted.
    • Cause
      • crates/chain-live/src/lib.rs:185-196 intentionally returns Vec::new() for RPC errors, missing storage, and decode errors. crates/validator/src/epoch_loop.rs:262 uses .get(i).copied().unwrap_or(false), making a permit-map fault indistinguishable from a known non-validator.
    • Fix
      • Fail closed for permit-map uncertainty in the payout guard: preserve permit-read success/failure in the metagraph/API (rather than collapsing it to an empty vector), and skip submission when the relevant permit entry cannot be established. At minimum, do not interpret an empty/short permit vector as a definite false for a pure-payout UID.

    T-Rex Ran code and verified through T-Rex

Reviews (1): Last reviewed commit: "fix(validator): extract submit outcome h..." | Re-trigger Greptile

Comment on lines +193 to +195
match raw {
Ok(Some(bytes)) => storage::decode_vec_bool(&bytes).unwrap_or_default(),
Ok(None) | Err(_) => Vec::new(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Permit-read failures bypass the payout guard

RPC failures, missing storage, and decode failures for ValidatorPermit are all converted into an empty vector. The sealed Match guard then treats the target UID's absent permit entry as a definite non-permit result, allowing a pure payout to a validator-permit hotkey to reach submit_intent. Preserve whether permit data was read successfully and skip submission whenever the target UID's permit status cannot be established.

Artifacts

Review-authored chain-live malformed ValidatorPermit test source

  • The captured review-authored wiremock test returns malformed ValidatorPermit SCALE storage and asserts the live metagraph still loads with no permit entries, proving the first half of the bypass.

Review-authored validator pure-payout submission test source

  • The captured review-authored validator test models the empty permit map after the read fault and asserts a sealed pure payout to UID 1 records one submission, proving the guard can consequently submit.

Chain-live malformed ValidatorPermit test output

  • This complete cargo-test output records the command, working directory, and exit code 0 for the wiremock metagraph test, showing malformed permit storage becomes an empty permit map.

Validator pure-payout submission test output

  • This complete cargo-test output records the command, working directory, and exit code 0 for the submission-path test, showing the empty permit map permits one recorded submission.

Captured test-source hashes

  • This command capture records hashes and line counts for the two uploaded review-authored source files, tying the executed proof to the uploaded sources.

View artifacts

T-Rex Ran code and verified through T-Rex

Comment on lines +283 to +293
if let Some(block) = metagraph_block {
if let Ok(hash) = chain.block_hash(block) {
if let Ok(mg) = chain.metagraph_at(&hash) {
return Ok(mg);
}
}
}
let tip = chain.current_block()?;
let hash = chain.block_hash(tip)?;
chain.metagraph_at(&hash)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Seal snapshot fallback misclassifies payouts

When a bundle supplies metagraph_block, an error fetching that historical snapshot is silently replaced with the current-tip metagraph. Owner and validator-permit status may have changed since sealing, so a pure payout that the seal-time guard would skip can be classified with newer state and submitted. Propagate the historical lookup failure when a seal block is present, and use the tip only when no seal block was supplied.

Artifacts

Review test source

  • Source of the temporary focused review test fixture and paired historical/pruned cases; it shows the exact `ChainClient` setup used to force the fallback and record the submission call.

Historical seal snapshot control run

  • Executed historical-snapshot control run; it reports `submission_calls=0`, proving the pure payout is skipped when the seal metagraph is readable.

Unavailable seal snapshot fallback run

  • Executed unavailable-snapshot fallback run; it reports `submission_calls=1`, proving fallback to the changed tip submits the sealed payout.

View artifacts

T-Rex Ran code and verified through T-Rex

primeorder 0.14.0 (via p256/dcap-qvl) pulled wnaf 0.14.0, yanked today
in favor of 0.14.1. Smallest lockfile bump to keep cargo-deny green.

Co-authored-by: Mathis <echobt@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants