Validator Configuration
Complete reference for all CLI arguments and environment variables used by the Minotaur validator.
Complete reference for all CLI arguments and environment variables used by the Minotaur validator (python -m minotaur_subnet.validator.main).
All settings can be provided as CLI arguments, environment variables, or a combination of both. CLI arguments take precedence over environment variables.
CLI Arguments
| Argument | Default | Description |
|---|---|---|
--port | 9100 | HTTP listen port for the validator API |
--epoch-seconds | 60 | Epoch duration in seconds for the local emit clock. Note: since PR #524, weight commit timing is tempo-aligned (one commit per chain tempo epoch, ~360 blocks), not driven by this wall-clock value — see TEMPO_ALIGNED_EMIT under Weight Emission. This still governs the fallback wall-clock cadence when tempo state is unqueryable. |
--store-path | None | Path to the store.json persistence file. If omitted, uses in-memory store. |
--tick-interval | 12.0 | BlockLoop tick interval in seconds (matches Ethereum block time) |
--subtensor-url | None | Subtensor WebSocket URL (e.g., wss://entrypoint-finney.opentensor.ai:443) |
--netuid | 112 | Bittensor subnet UID |
--wallet-name | None | Bittensor wallet name |
--hotkey-name | None | Bittensor hotkey name |
--validator-key | "" | EVM private key (hex) for EIP-712 consensus signing |
--validator-registry-address | "" | Address of the on-chain ValidatorRegistry — the canonical source of quorumBps (and the authorized validator set). Read at startup and refreshed once per epoch. There is no --quorum-bps flag; quorum is not set on the CLI. |
--leader-api-url | None | Leader API base URL to sync the app catalog from (e.g. https://api.minotaursubnet.com). Required for follower validators that don’t receive create_app / deploy_app calls directly. Falls back to LEADER_API_URL env. |
--app-sync-interval | 60.0 | Seconds between app catalog sync ticks. |
Environment Variables
Bittensor Identity
| Variable | Default | Description |
|---|---|---|
NETUID | 112 | Subnet UID. Set to 1 for local testnet. |
WALLET_NAME | — | Bittensor wallet name (same as --wallet-name) |
HOTKEY_NAME | — | Bittensor hotkey name (same as --hotkey-name) |
BT_WALLET_PATH | — | Wallet root directory (parent of <WALLET_NAME>/hotkeys/<HOTKEY_NAME>). Unset → SDK default $HOME/.bittensor/wallets (in-container /home/minotaur/.bittensor/wallets for uid 1000). Set this when the wallet is mounted elsewhere, or when the default lookup can’t see/read it — a wrong path or a mount not readable by uid 1000 is the usual cause of weights_emitter_configured=false (a silent dead emitter). The hotkey file must be readable by uid 1000. Alias: WALLET_PATH. |
SUBTENSOR_URL | — | Subtensor WebSocket endpoint (same as --subtensor-url). Accepts the alias finney (= public wss://entrypoint-finney.opentensor.ai:443) or any explicit ws:///wss:// URL. If you operate your own subtensor node, point this at it (e.g. ws://your-subtensor:9944) to avoid the per-IP rate limits on the public endpoint — see Run your own subtensor in the quickstart. |
Simulation (Anvil)
| Variable | Default | Description |
|---|---|---|
ANVIL_RPC_URL | — | URL of the Ethereum Anvil fork the validator should connect to for plan simulation. The validator does not spawn Anvil — start it separately (see the Validator Quickstart, Step 5). Usually http://localhost:8545 when Anvil runs on the same host. |
BASE_RPC_URL | — | URL of the Base Anvil fork the validator should connect to (chain ID 8453). Started separately, same pattern as ANVIL_RPC_URL. |
BITTENSOR_EVM_RPC_URL | — | URL of the BT EVM Anvil fork the validator should connect to (chain ID 964). Started separately. |
ETH_UPSTREAM_RPC_URL | — | Upstream Ethereum RPC (e.g. Alchemy/Infura) that the validator uses to advance the local Anvil fork to current head between simulations. Without it, the fork stays frozen at startup. |
BASE_UPSTREAM_RPC_URL | — | Upstream Base RPC, same role as ETH_UPSTREAM_RPC_URL for the Base fork. |
BITTENSOR_EVM_UPSTREAM_RPC_URL | — | Upstream BT EVM RPC, same role for the BT EVM fork. Defaults to the public https://lite.chain.opentensor.ai (rate-limited per source IP). A self-hosted subtensor node serves the EVM JSON-RPC on the same port as its substrate RPC (Frontier is built into the binary); if you set SUBTENSOR_URL=ws://your-subtensor:9944 you can typically set this to http://your-subtensor:9944 against the same host. Use http/https here — Anvil’s --fork-url does HTTP polling, not WS. |
Benchmark Performance
| Variable | Default | Description |
|---|---|---|
BENCHMARK_CONCURRENCY | 1 | Number of isolated solver runtimes to shard each benchmark across (the scenario pool). 1 (default) is the byte-identical sequential path — the kill-switch: leave/set at 1 to instantly revert with no code change. K>1 runs scenarios concurrently for roughly K× on the network-latency-bound segment (rounds are ~90% CPU-idle), each runtime fully isolated (own solver container + own block-pin proxy session + own read budget). Per-validator, NOT consensus — K is never folded into the benchmark pack hash, so a fleet running mixed K computes identical scores (no coordination needed). Each runtime costs ~1 solver container (≈4 GB / 2 CPU); the practical ceiling is upstream archive-RPC concurrency, not validator RAM. Recommended 2–4; hard-clamped to [1, 63]. Roll out by bumping one validator, confirming byte-identical scores vs a K=1 peer + faster wall-clock, then the fleet. |
RPC_PROXY_UPSTREAM_MAX_CONCURRENCY | 24 | (block-pin proxy server) Max concurrent upstream connections the proxy opens to the archive RPC. Bounds the read storm at high BENCHMARK_CONCURRENCY so the provider isn’t rate-limited into per-validator timeouts (a non-determinism source). Tune to your RPC tier; ≈ K × a few reads per scenario. |
Consensus and Signing
| Variable | Default | Description |
|---|---|---|
VALIDATOR_PRIVATE_KEY | "" | EVM private key (hex, with 0x prefix) for EIP-712 consensus signing (same as --validator-key) |
VALIDATOR_AXON_URL | — | Public URL where this daemon serves the /identity endpoint, e.g. http://your-host:9100. Used by peer discovery: the daemon signs this URL into its /identity attestation so other validators can verify the binding. If unset, /identity returns 503 and other validators can’t include you in their peer set. |
VALIDATOR_REGISTRY_8453 | — | Required. Address of the on-chain ValidatorRegistry on Base (chain 8453). Holds the authorized validator EVM list + canonical quorumBps for order-consensus; the daemon reads both at startup and refreshes once per epoch. See Quorum management. |
VALIDATOR_REGISTRY_964 | — | Required. Same contract on BT EVM (chain 964). Used by the api service for champion-consensus signer verification. |
VALIDATOR_REGISTRY_ADDRESS | — | Legacy single-chain form. Deprecated — use the chain-specific VALIDATOR_REGISTRY_<chain> variables above. The canonical .env.example ships the chain-specific forms with current production addresses pre-filled. |
QUORUM_BPS_OVERRIDE | — | Emergency / local-testnet escape hatch: forces a local quorum value and skips the on-chain read. Production deployments should leave this unset so ValidatorRegistry.quorumBps() stays authoritative. |
ORDER_CONSENSUS_PEERS | "" | Internal-only escape hatch. Pinned-peer list (addr@url, comma-separated) for order-consensus. Bypasses automatic discovery. Used only by the subnet team’s prod (where metagraph axons aren’t published yet) and by test harnesses. Third-party validators should always leave this unset — discovery via the metagraph + on-chain ValidatorRegistry is the supported path. |
CHAMPION_CONSENSUS_PEERS | "" | Internal-only escape hatch. Same pattern for champion-consensus. Same warning: third-party validators should leave it unset. |
Leader Election
| Variable | Default | Description |
|---|---|---|
FORCE_LEADER | "" | Set to "1" to force this validator to act as the leader, bypassing stake-based election. Useful for local testnet. |
App Catalog Sync
The follower validator pulls AppIntentDefinition (including js_code) and DeploymentResult records from the leader’s API on a poll interval and writes them into the local AppIntentStore. Without this, a third-party validator’s JsExecutionEngine has no scoring code loaded and cannot re-score incoming consensus proposals.
Since PR #584, sync also propagates deletions: after a successful, non-empty catalog fetch a follower prunes local apps the leader no longer lists — but only non-operational ones (no deployment / non-operational status); an app the follower can actively score against is never auto-deleted on a single listing (it logs a loud warning instead), and an empty catalog never mass-deletes. Deleting an app now cascades its deployment rows. The leader never self-syncs, so the source-of-truth store is untouched.
| Variable | Default | Description |
|---|---|---|
LEADER_API_URL | — | Leader API base URL (e.g. https://api.minotaursubnet.com). Set on every third-party validator. Leaders should leave this unset — they are the source of truth and would otherwise sync from themselves. |
--app-sync-interval (CLI only) | 60.0 | Seconds between sync ticks. |
Trust model (MVP): js_code is fetched from the leader and trusted as-is. There is no on-chain hash anchor at this layer, so a compromised leader could push malicious JS to followers. Anchoring keccak256(js_code) on-chain via AppRegistry is a tracked follow-up; until then the daemon emits a SECURITY NOTICE log at startup whenever sync is enabled.
Weight Emission
SN112 weights are commit-reveal: the chain keeps only one pending commit per validator per tempo epoch (≈360 blocks) and silently discards earlier commits in the same epoch. PR #524 schedules all emission into a short window just before the epoch step so the commit is the last of its tempo and reveals with the freshest champion snapshot.
| Variable | Default | Description |
|---|---|---|
TEMPO_ALIGNED_EMIT | 1 (ON) | Tempo-aligned weight commits. Set to 0/false/no to restore the legacy wall-clock cadence (every --epoch-seconds, plus an immediate emit on each round activation). When the chain tempo state can’t be queried, the gate falls back to exact legacy behavior automatically. |
TEMPO_EMIT_LEAD_BLOCKS | 20 | Size of the pre-step emit window in blocks (~4 min). The commit fires this many blocks before the tempo boundary. |
/health reports the current emit_schedule (mode / active / tempo / next boundary); mode is "wall_clock" when tempo alignment is disabled or unavailable.
Deployment Benchmarking
| Variable | Default | Description |
|---|---|---|
BENCHMARK_ALL_DEPLOYMENT_CHAINS | 1 (ON) | Consensus flag — must be fleet-uniform. Default ON is image-baked (PR #806): third-party validators run the canonical compose without custom env, so the default lives in code, not env. When ON, submissions are benchmarked per-deployment-chain with per-chain fork pins folded into benchmark_pack_hash. Set to 0/false/no/off to restore byte-identical Base-only benchmarking. A mixed fleet computes different scores → PACK_HASH_MISMATCH (fail-loud); flip fleet-uniformly on a round boundary. Arming it requires the extra chains routed through the block-pin proxy (SOLVER_READ_PROXY_CHAINS), a live upstream RPC for pin derivation, and a sim fork (ETH_SIM_RPC_URL). |
ETH_SIM_RPC_URL | — | Optional chain-1 (Ethereum) simulation fork URL used when deployment benchmarking spans Ethereum. |
Distributed Veto (Phase 1 — enforcing by default)
Distributed veto is enforcing by default (image-baked, PR #675/#925) — it is
no longer observe-only. Followers independently re-check the leader’s champion
candidate; a leader-confirmed violation ABORTS certification. Blocking fires
only on the leader’s own reverify (would_gate_confirmed), never on a raw
follower claim, and the gate fails open on deadline expiry or a slow fleet —
so the worst case degrades to the old observe-only behavior and it never wrongly
aborts on a laggy peer. Consensus-relevant; best left at the defaults unless
the subnet team directs otherwise.
| Variable | Default | Description |
|---|---|---|
DISTRIBUTED_VETO | 1 (ON) | Master participation switch — default ON so every validator joins the veto pass. Set to 0/false/no/off to opt out. /health surfaces the last few records under distributed_veto. |
DISTRIBUTED_VETO_ENFORCE | hard (image-baked) | Enforcement mode. hard = a leader-confirmed veto ABORTS certification. shadow = run the pre-certify gate for real (same added latency) but only LOG “would block” and certify anyway. off = observe-only (never gates). An unrecognized value maps to hard — a mistyped override can’t silently disarm a validator. |
DISTRIBUTED_VETO_REVERIFY | 1 (ON) | Leader re-verification sub-switch — gives the hard gate its teeth (blocking only ever happens on a reverify-confirmed violation). With this off, the gate is toothless and fail-opens every round. Set to 0/false/no/off to disable. |
Chain Configuration
| Variable | Default | Description |
|---|---|---|
CHAIN_ID | 31337 | Default EVM chain ID when unset (local Anvil testnet). Set to 1 for Ethereum mainnet — the canonical production .env sets this explicitly. |
Logging
| Variable | Default | Description |
|---|---|---|
LOG_LEVEL | INFO | Log level: DEBUG, INFO, WARNING, ERROR |
Docker Configuration (Local Testnet)
When running in the local testnet via Docker Compose, the validator is configured as follows:
validator: command: >- python -m minotaur_subnet.validator.main --port 9100 --store-path /data/store.json environment: ANVIL_RPC_URL: http://anvil:8545 BASE_RPC_URL: http://anvil-base:8546 SUBTENSOR_URL: ws://subtensor:9944 NETUID: "1" WALLET_NAME: validator HOTKEY_NAME: default VALIDATOR_PRIVATE_KEY: "${VALIDATOR_KEY_0}" # see platform/local_testnet/.env.example QUORUM_BPS: "10000" CHAIN_ID: "31337" FORCE_LEADER: "1" volumes: - testnet-config:/config:ro - store-data:/data - ~/.bittensor/wallets:/root/.bittensor/wallets:roKey points:
FORCE_LEADER=1makes the validator act as leader immediately (no stake-based election on local testnet).CHAIN_ID=31337is the Anvil local chain ID.NETUID=1is the local subnet (not mainnet’s 112).- The store volume (
store-data) is shared between the API and validator containers. - Wallet directory is mounted read-only from the host.
Example: Production .env
# BittensorNETUID=112WALLET_NAME=my-validatorHOTKEY_NAME=my-hotkeySUBTENSOR_URL=wss://entrypoint-finney.opentensor.ai:443
# SimulationANVIL_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR_ALCHEMY_KEYBASE_RPC_URL=https://base-mainnet.g.alchemy.com/v2/YOUR_ALCHEMY_KEY
# Consensus — peer set comes from on-chain ValidatorRegistry + metagraph,# not from env. Just supply the signing key + the per-chain registry# addresses (both chains are required — the canonical .env.example ships# current values pre-filled).VALIDATOR_PRIVATE_KEY=0xYOUR_EVM_PRIVATE_KEYVALIDATOR_REGISTRY_8453=0x88a08d1105393EACE9B6f5ff678DbE508B8639aCVALIDATOR_REGISTRY_964=0x0B5fE44e90515571761D86C28c4855F325EDE098QUORUM_BPS=10000
# ChainCHAIN_ID=1
# LoggingLOG_LEVEL=INFOPrecedence Rules
- CLI arguments take precedence over environment variables.
- For
--netuid, the CLI value is used only if it differs from the default (112); otherwise theNETUIDenvironment variable is checked. - Quorum is not a CLI flag. The canonical
quorumBpsis read from the on-chainValidatorRegistry(via--validator-registry-address/VALIDATOR_REGISTRY_<chain>);QUORUM_BPS_OVERRIDEis the only local escape hatch (it skips the on-chain read).
See also: Quickstart, Troubleshooting.