Skip to content

fix: bring production hotfixes into main; algorithm 3 empty-epoch burn - #313

Merged
Mathis (echobt) merged 4 commits into
mainfrom
fix/prod-hotfix-v3
Sep 25, 2026
Merged

Mathis (echobt) merged 4 commits into
mainfrom
fix/prod-hotfix-v3

Conversation

@echobt

@echobt Mathis (echobt) commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This PR brings the fixes that only live on the production master into main, and fixes one conflict they have with algorithm 3.

Today the master (cortex-deploy, /opt/cortex) runs the fix/hotkey branch (af44660) plus an uncommitted change to src/cortex/master.py. Neither exists on main, so deploying main, which is needed for the challenge supervisor and OpenType, would silently drop them. This PR:

  1. b8be35d — cherry-picks af44660 unchanged: the validator loads its hotkey from privateKey, and a zero-miner epoch burns only the declared shares.
  2. b6be281 — commits the uncommitted production change to master.py: the end of a completed epoch is found even when the chain has pruned the state at the remembered start block. On mainnet, epoch 25267 had stopped sealing because of this.
  3. d9d7886 — new fix. Under algorithm 3 each share is scaled by its claimed score. In an epoch where nobody scored, the declared mass is therefore 0, the zero-miner path raised no challenge carries an emission share, and sealing failed. That epoch now burns the whole vector. The e2e test compares normalized weights, as the chain does.

The trust-root edits in the production working tree (config/*.toml, .sig, owner.pubkey, an unsigned template) are not included. They are operator material handled by the offline ceremony.

Tests

  • ruff format, ruff check and the repository contracts pass.
  • pytest: 1100 passed.
  • New test: test_v3_with_no_score_burns_everything.

🤖 Generated with Claude Code

RetriggerConfidence Score: 5/5

No outstanding findings block merging.

Summary

This PR includes private-key validator hotkey loading, historical epoch-end lookup, and empty-epoch burn handling. The three previously reported validator issues are fixed in the current code.

Reviews (2) · Last reviewed commit: "fix(validator): signable private-key hot..."

Mathis (echobt) and others added 3 commits September 25, 2026 11:30
Two changes, both needed to run a validator whose hotkey is an exported
Polkadot keystore and whose subnet has no miners yet.

# The hotkey file decides how it is read

`bittensor_wallet.Wallet` derives a hotkey from the BIP-39 mnemonic in
`secretPhrase` and overwrites `privateKey`, `publicKey` and `ss58Address` with
whatever that phrase produces. A keystore key is a 64-byte expanded secret and
no mnemonic produces it. Measured against the wallet library: a file carrying
only `privateKey` raises `KeyFileError: Invalid phrase`, and a file carrying a
valid phrase beside a foreign `privateKey` silently reloads the phrase's key.

A keystore lives at the path a mnemonic wallet already uses, so the command
line does not change and the deploy gate keeps auditing the surface it audited.
`carries_private_key` reads the file: a `secretPhrase` means the wallet library
owns it, a usable `privateKey` without one means this loader does. The loader
builds only what the submit path asks of a wallet and refuses a public or
symlinked file, a key that is neither 32 nor 64 bytes, a non-sr25519
`cryptoType`, and a key whose public half does not match the ss58 it declares.

# The consensus seed is checked only where it is read

`consensus_seed()` ran unconditionally. That seed signs cross-validator root
statements and dissents, which exist only under `--peer-consensus`. A
gateway-backed validator therefore failed at startup over a value it would
never read. The check now runs only with `--peer-consensus`.

# The zero-miner burn carries the declared allocation

With no miner claiming anything, the vector was padded across arbitrary uids at
equal weight, which said nothing about the challenge document: bounty and proof
burned alike. The burn now carries the declared fractions, so the proof share is
what burns when nothing is claimed. The chain's minimum-weight count is still
met; only the mass changed.

Verified: 55 tests pass; each guard checked by reintroducing its defect and
watching the test fail; ruff, mypy, check_repo and check_deploy clean; against a
real 64-byte operator key the loader derives its declared ss58 and signs a
64-byte signature `sr25519.verify` accepts.

Still unproven: no weight submitted to a live subnet from a private-key wallet,
and no bundle with a non-zero miner set aggregated here. Nine tests in proof/,
test_master and test_network_e2e fail on origin/main here and are untouched.

Co-Authored-By: Claude Code <noreply@anthropic.com>
The zero-miner burn fix spreads only the declared shares. Under algorithm 3 every
share scales with its claimed score, so an epoch nobody scored in declares no mass
and sealing failed; that epoch now burns in full. The e2e weight check compares
normalized weights, as the chain does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment thread src/cortex/validator/keystore.py
Comment thread src/cortex/validator/keystore.py
Comment thread src/cortex/validator/__main__.py Outdated
@greptile-apps

This comment has been minimized.

- Hotkey exposes crypto_type (sr25519), which the substrate signer reads first
- a hotkey file whose secretPhrase does not derive its declared ss58Address is
  refused instead of silently signing as another account (btcli files carry both)
- --wallet-name and --wallet-hotkey are required

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@echobt

Copy link
Copy Markdown
Contributor Author

The outside-diff findings (missing crypto_type; missing wallet options) are the same two issues as the inline threads, both fixed in 90a8aca.

Greptile (@greptileai) please re-review.

@echobt
Mathis (echobt) merged commit 163f814 into main Sep 25, 2026
6 checks passed
Mathis (echobt) added a commit that referenced this pull request Sep 26, 2026
main (#313) already carries this branch's privateKey keystore (with the
secretPhrase/ss58Address check and crypto_type), the algorithm 3
empty-epoch burn, and the master background-failure logging. Conflicts
resolved to main's side; the duplicate zero-miner burn is dropped.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.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.

1 participant