Releases: BitMind-AI/bitmind-subnet
Release 4.6.0
feat: Discriminator Verticals & Miner Performance CLI
Summary
Introduces the vertical axis to the discriminator competition (starting with image:human), adds a self-service performance endpoint for miners, adjusts incentive allocations, and cleans up deprecated code.
Changes
Vertical support in model uploads
gascli d pushnow accepts--vertical human(only valid with--modality image).- Client-side guards in both
gas/cli.pyandpush_model.pyprevent invalid vertical+modality combinations before hitting the API. gas/protocol/model_uploads.pyrenamed togas/protocol/miner_requests.pyto reflect its broader scope.
Miner performance CLI (gascli d perf)
- New
gascli d perfcommand lets miners query their own benchmark results via an Epistula-authenticated GAS API endpoint. - Output shows SN34 score (with progress bar), MCC, Brier, modality, vertical, and elapsed time for running/queued jobs.
- Supports
--modalityand--verticalfilters. - Core HTTP logic lives in
gas/protocol/miner_requests.py; CLI handles wallet loading, auth, and formatting.
Incentive allocation adjustment
- Image allocation increased from 23% → 28.5% to accommodate the new
image:humanvertical (~17.5% of image rewards). - Video allocation adjusted from 29% → 23.5%.
- Burn, audio, and generator percentages unchanged.
Fix: Non-resumable upload flow and silent 409 handling
The upload flow (push_model.py / upload_single_modality) had no way to distinguish a genuine failure from "your model was already accepted by the server." Miners who re-ran the script after a partial failure (e.g. chain registration timed out) would hit a 409 from /upload/presigned and receive an opaque error, unable to proceed.
What was wrong: The server returns 409 when the file hash is already in an accepted state (uploaded, downloading, validating, examining, or confirmed). Both 409 and genuine errors were handled identically — print("FAILED") and exit — even when the model was already on the server and the miner just needed to retry blockchain registration.
Fix: upload_single_modality in gas/protocol/miner_requests.py now returns an already_uploaded: True flag on 409. push_separate_models in push_model.py checks this per modality and prints a clear, actionable message:
⚠️ Video model already uploaded — skipping (model is already accepted by the server)
❌ Cannot retrieve r2_key for already-uploaded video model. Re-run with the
original r2_key or wait for the current upload to be processed.
Note: The miner still can't fully auto-resume from a 409 because the server doesn't return the original r2_key on conflict (by design — information leak). A full solution would require a "status lookup by hash" endpoint that returns the r2_key for models owned by the requesting hotkey.
Release 4.5.6
C2PA trust anchor validation for c2pa-python >= 0.29.0
Background
c2pa-python==0.29.0 (released March 17 2026) changed c2pa-rs so that trust anchor checking is always enforced by default. Any certificate not chaining to a root CA in c2pa-rs's built-in store fires signingCredential.untrusted and hard-fails validation.
We had pinned c2pa-python>=0.25.0,<0.29.0 as a short-term workaround. This restores previous behavior but leaves a cert-forgery gap: our code only string-matched on the issuer field, which an attacker could fake with a self-signed cert set to "Stability AI Ltd".
What this PR does
1. Extracts and saves trust anchor PEM files
Scanned miner-submitted media in /workspace/.cache/sn34 (88k entries, source_type = 'miner') to find one sample file per AI provider. Extracted the full DER certificate chains embedded in each file's C2PA JUMBF box and saved them to gas/verification/trust_anchors/.
All major providers turned out to use private or non-CAI root CAs that are not in c2pa-rs's built-in store:
| Provider | Cert chain root | File |
|---|---|---|
| Stability AI | GlobalSign Root CA - R6 (via GlobalSign GCC R6 SMIME CA 2023) | stability_ai.pem |
| Runway (pre-Gen4) | GlobalSign Root CA - R6 (same intermediate) | runway.pem |
| Runway Gen4 | CN=Stability AI, O=Stability AI, C=US (private self-signed) |
runway_gen4.pem |
| Black Forest Labs | GlobalSign Root CA - R6 (same intermediate) | black_forest_labs.pem |
| Microsoft | CN=Microsoft Supply Chain RSA Root CA 2022 (private self-signed) |
microsoft.pem |
| OpenAI (via Truepic) | CN=Truepic WebClaimSigningCA chain |
openai_truepic.pem |
| Adobe | CN=Adobe Product Services G4 chain |
adobe.pem |
CN=Google C2PA Root CA G3 (fetched from pki.goog/c2pa/root-g3.crt) |
google_c2pa.pem |
GlobalSign Root CA - R6 was sourced from the certifi bundle. Google's C2PA Root CA G3 was fetched directly from Google's PKI at http://pki.goog/c2pa/root-g3.crt.
2. Updates c2pa_verification.py
Three additions at the top of the file, zero changes to existing verification logic:
_load_trust_anchors()— concatenates all PEM files at import time into a single string_TRUST_ANCHORS_PEM/_C2PA_HAS_CONTEXT_API— cached module-level constants (no per-call file I/O)_open_c2pa_reader(file_path)— transparent wrapper aroundc2pa.Reader:- On >= 0.29.0: builds a
Settingsobject withuser_anchors(additive — preserves the built-in store) and wraps the reader in aContext - On < 0.29.0: returns a bare
c2pa.Reader, identical to previous behaviour
- On >= 0.29.0: builds a
The one-line change in verify_c2pa():
# before
with c2pa.Reader(file_path) as reader:
# after
with _open_c2pa_reader(file_path) as reader:3. Bumps the version pin
# pyproject.toml
- "c2pa-python>=0.25.0",
+ "c2pa-python>=0.29.0",Verification
Ran gascli g verify-c2pa against one sample file per provider on c2pa-python 0.29.0:
Adobe (Adobe Inc.)
Black Forest Labs (Black Forest Labs Inc.)
Runway Gen4 (Runway — private Stability AI root)
Google (Google LLC — Google C2PA Root CA G3)
Google (Google C2PA Core Generator Library)
Microsoft (Microsoft Corporation — video)
Microsoft (Microsoft Corporation — image)
OpenAI (Sora / Truepic chain)
OpenAI (OpenAI-API)
Runway (RUNWAY AI, INC. — pre-Gen4)
One Stability AI sample showed assertion.dataHash.mismatch — the cached file was modified after signing, which is correct behaviour (hash mismatch ≠ trust failure).
Security improvement
With c2pa-python>=0.25.0,<0.29.0, c2pa-rs skipped the trust chain check entirely. Our code fell back to string-matching on issuer, meaning an attacker could submit media signed with their own self-signed cert and set issuer = "Stability AI Ltd" to pass validation.
With this PR, c2pa-rs performs full cryptographic chain verification against the extracted root certs. Forging a cert that chains to CN=Stability AI, O=Stability AI (Runway Gen4's root) or GlobalSign Root CA - R6 requires breaking RSA-2048/PSS — the issuer string alone is no longer sufficient.
Release 4.5.2
Validators can now run as Docker containers alongside the existing pm2 setup. pm2 remains the default — Docker is an explicit opt-in via --docker on all gascli validator commands.
Running with pm2 (default)
gascli v start
gascli v stop
gascli v logsRunning with Docker
gascli v start --docker
gascli v stop --docker
gascli v delete --docker
gascli v status --docker
gascli v logs --dockerOr directly via Compose:
docker compose --env-file .env.validator up -dArchitecture
Three containers share a single bitmind-subnet-validator image, each running one service (controlled by the SERVICE env var):
| Container | Role |
|---|---|
validator |
Core validation loop, FastAPI callback server |
generator |
Image/video generation (GPU-enabled) |
data |
Dataset downloads and cache management |
Configuration
.env.validator is the single source of truth for both container environment and Compose variable substitution (bind-mount paths, ports, etc.). Copy the template and fill in your details:
cp .env.validator.template .env.validatorKey fields:
| Variable | Description |
|---|---|
WALLET_PATH |
Host path to your Bittensor wallets directory |
WALLET_NAME / WALLET_HOTKEY |
Only that wallet's subdirectory is bind-mounted |
BT_LOGGING_LOGGING_DIR |
Base path for validator state and Bittensor logs |
SN34_CACHE_DIR |
Model/data cache — shared between pm2 and Docker, no re-downloading when switching |
HF_HOME |
Hugging Face cache — also shared with pm2 |
CALLBACK_PORT |
Port miners call back to; exposed via network_mode: host |
NETUID |
Auto-derived from CHAIN_ENDPOINT; set explicitly to override |
Persistent state
Scores, challenge tasks, and Bittensor logs are bind-mounted from the host under BT_LOGGING_LOGGING_DIR/WALLET_NAME/WALLET_HOTKEY/ and survive container restarts.
Automatic updates
A cron-friendly docker/autoupdate.sh script pulls the latest image and restarts containers:
*/5 * * * * /path/to/bitmind-subnet/docker/autoupdate.sh >> /var/log/bitmind-docker-update.log 2>&1Viewing logs
# via gascli
gascli v logs --docker
# directly (per service)
docker compose logs -f validator # or generator, dataRelease 4.4.7
Release notes: 4.4.1 – 4.4.7
4.4.1 (30 Jan 2026)
- Validator weight normalization: Only assign non-zero weights to UIDs in the active set; explicitly zero out weights for inactive generative miners before scaling.
4.4.2 (31 Jan 2026)
- C2PA verification: Broader C2PA issuer handling so more certificate issuers are accepted as trusted.
- Allow all certificate issuers to be treated as CA issuers where appropriate.
- Remove redundant issuer logic and consolidate issuer handling.
- Increase C2PA options and clean up multiple issuer versions.
4.4.3 (3 Feb 2026)
- New upload endpoint for discriminative miner model uploads (
neurons/discriminator/push_model.py). - Validator: Decrease burn (burn rate reduced).
4.4.4 (4 Feb 2026)
- Generator prompt sampling: Keep
remove=Falseso prompts are not removed when sampled; usethreading.Lockfor thread-safe prompt sampling. - Content cache and uploads: New content DB and content manager support plus a new upload path used by the data service.
- Config: New options for content/upload behavior (see
.env.validator.template). - Docs: Updates to Discriminative-Mining, ONNX, and Validating.
4.4.5 (6 Feb 2026)
- Generator rewards: Use a 2-hour lookback window for recent verified miner media when computing generator base rewards (eligibility and score stability).
- Content cache: Content DB and content manager extended to support the new lookback and verification stats.
4.4.6 (6 Feb 2026)
- Stability AI service: Fix request headers for the Stability AI miner service so API calls succeed.
- Webhooks / logging: Remove unused
has_c2pakey from logging; keep useful logging; reduce log noise. - Metagraph / validator: Minor adjustments for consistency and clarity.
4.4.7 (8 Feb 2026)
- Verification stats: Add support for failed miner media in the lookback window.
- New
get_recent_failed_miner_media()in content DB and content manager (default 2-hour lookback). - New
get_verification_stats_last_n_hours()to compute pass/fail counts and pass rate per miner over the last N hours (for generator base rewards and eligibility).
- New
- Content manager: New helper
_build_verification_stats_from_verified_and_failed()to build per-miner verification stats from both verified and failed media. - Validator: Uses the new verification stats and failed-media lookback for more accurate generator rewards and eligibility.
Release 4.4.0
Summary
- Fix generator reward logic for sharper penalties when generative miners stop responding to queries
- Improve media format detection with proper ftyp-based MP4 detection and support for additional formats
- Fix file format handling - detect actual format from magic bytes instead of hardcoding extensions/content-types
- Add dynamic escrow address fetching from the API with fallback to defaults
- Add prompt modality tracking to ensure prompts are matched to their intended modality
- Add prompt cache cleanup to delete prompts after use, preventing unbounded cache growth
- Add detailed error responses for generative callback failures so miners can debug issues
- Add webhook stats tracking for miners to monitor success/failure rates per validator
- Add C2PA verification CLI for testing C2PA credentials on local files
- Remove LocalService from registry (since it doesn't produce C2PA-signed content)
Changes
Generator Miner Rewards Fix
- Fixed bug where
include_all=Truewas double-counting already-rewarded media in verification stats - Changed reward eligibility from union to intersection: miners must now have both local verified submissions and benchmark results to receive rewards
- Changed base reward default from
1e-4to0for generators without verified submissions - Increased EMA alpha from
0.2to0.5for more aggressive score decay on inactive miners - Added hard cutoff to zero out scores for generators not active within the liveness window
Media Format Detection (#311)
- Fixed MP4 detection by checking for
ftypat offset 4 instead of hardcoded box sizes - Consolidated all ftyp-based format detection to fix unreachable M4A code path
- Added support for new image formats: HEIC/HEIF, AVIF, GIF, BMP, TIFF
- Added support for new video formats: MOV, 3GP, AVI
- Added support for audio formats: MP3, WAV, FLAC, OGG, M4A
- Added length checks to prevent index errors on short data
- Improved JPEG detection with 3-byte signature
Dynamic Escrow Addresses (#322)
- Added new
get_escrow_addresses()function to fetch escrow addresses from the gas-api to provide the option to punish weight copiers. - Validators now dynamically fetch
video_escrow,image_escrow, andaudio_escrowaddresses - Escrow address eligibility based validators provably running latest SN code.
Prompt Modality Support
- Added
modalitycolumn to prompts table (image,video,audio) - Database migration automatically adds column to existing installations
- Fixed database migration order to prevent "no such column: modality" errors on existing databases
- Prompts are now stored with their intended modality when generated
- Challenge manager selects modality first, then samples matching prompts
Prompt Cache Cleanup
- Fixed
removeparameter in prompt sampling to actually delete prompts instead of just incrementingused_count - Added
min_prompts_threshold(default 100) to prevent running out of prompts - Enabled prompt cleanup in generative challenge manager - prompts are now deleted after use (if enough remain)
Generative Callback Error Responses
- Miners now receive detailed error messages when their submissions are rejected:
400 - Empty binary payloadfor empty uploads400 - Corrupted or unreadable mediafor invalid media files400 - Duplicate content detectedfor perceptual hash matches400 - C2PA verification failed: no C2PA manifestfor missing credentials400 - C2PA verification failed: untrusted issuerfor non-trusted sources
- Previously, all validation failures returned
200 OKsilently, making it impossible for miners to debug issues - Made
c2paa hard dependency - removed conditionalC2PA_AVAILABLEchecks - Cleaned up inline imports, moved all imports to module scope
Miner Webhook Stats Tracking
- Added
WebhookStatsTrackerto track webhook success/failure rates per validator IP - Stats are persisted to
~/.bitmind/webhook_stats.jsonand survive restarts - Automatic behaviors:
- Saves to disk every 60 seconds (debounced to avoid excessive I/O)
- Prints summary to logs every 5 minutes
- Daily rotation at midnight - archives to
webhook_stats_archive/webhook_stats_YYYY-MM-DD.json - Auto-cleanup of archives older than 7 days
- Failure types are categorized:
empty_payload- validator received empty dataconnection_timeout- connection timed outconnection_error- network/connection issueshttp_400,http_401,http_404,http_500- HTTP status codes
- Helper functions available:
print_webhook_stats()- print summary to logsget_webhook_stats_json()- get stats as dictreset_webhook_stats()- clear all stats
LocalService Removed from Service Registry
- LocalService (local Stable Diffusion/AnimateDiff) removed from
SERVICE_MAP - Reason: LocalService doesn't produce C2PA-signed content, which validators now require
- The service class remains in the codebase for potential future use
- Valid services are now:
openai,openrouter,stabilityai, ornone - If no valid service is configured for a modality, requests for that modality will be rejected
- Miners still free to generate whatever
C2PA Verification CLI
- Added
gascli generator verify-c2pa(standalone helper script atneurons/generator/helper/verify_c2pa.py) command for gen miners to test local files for C2PA credentials - Useful for miners to verify their generated content has valid C2PA before sending to validators
- Supports
--verbosefor detailed output and--jsonfor machine-readable output
Release 4.3.0
This release introduces c2pa verification for miner submissions (+ the relevant support on the generative miner side), an upgraded prompt generation pipeline, and fixes for weight normalization in the validator.
Generative Miner Data Verification
New modules in gas/verification/:
-
c2pa_verification.py: Validates C2PA content credentials from miner-submitted media. Checks that content originates from trusted AI generators (OpenAI, Google, Adobe, Microsoft, Stability AI, Midjourney, Anthropic). Content without valid C2PA from trusted issuers is rejected. -
duplicate_detection.py: Implements perceptual hashing (pHash) for images and frame-based hashing for videos. Includes crop-resistant hash segments. Configurable Hamming distance threshold (default: 8) for near-duplicate matching.
Miner Service Configuration
- Added
IMAGE_SERVICEandVIDEO_SERVICEenvironment variables to specify which generation service to use per modality - Valid options:
openai,openrouter,local,none - Setting
nonedisables that modality entirely - Prevents loading unnecessary local models when using API services
- Conditional model loading based on configured services
Prompt Generation Pipeline
- Replaced BLIP-2 with Qwen2.5-VL-3B-Instruct for image annotation
- Optional flash attention support for faster inference
- New modules for model-specific prompt styles (
model_prompt_styles.py) and prompt modifiers (prompt_modifiers.py) - Increased max token output from 20 to 256 for richer descriptions
Challenge Manager Improvements
- Pre-storage validation rejects: duplicate content, content without valid C2PA, corrupted/unreadable media
- Duplicate checks now scoped to same prompt ID to reduce overhead
- Only validated content is stored and eligible for HuggingFace upload
Service Improvements
- C2PA metadata preservation in OpenRouter and OpenAI services
- Option to save generated media locally (
MINER_SAVE_LOCALLY) - Default model changed to nanobanana pro for OpenRouter
Validator Weight Normalization Fix
- Fixed burn rate calculation by excluding special UIDs (burn, image escrow, video escrow) from weight normalization (previously burning more than .7)
- Added null check for empty generator rewards
- Added logging for actual vs target burn rate verification
Dependencies
- Added:
c2pa-python>=0.25.0,imagehash>=4.3.1,qwen-vl-utils>=0.0.14 - Updated:
async-substrate-interface>=1.5.1 - Optional:
flash-attn>=2.7.0(commented, for manual installation)
Breaking Changes
- For now, miners must now produce content with valid C2PA credentials from trusted AI generators.
- Duplicate submissions within the same prompt are rejected
- Until we harden verification of open source model outputs, miners using local generation without C2PA embedding will have submissions rejected
- Current services supported in c2pa checks:
OpenAI
Google
Adobe
Microsoft
Meta
Shutterstock
Canva
Runway
Stability AI
Pika Labs
Reach out to the BitMind team if there are more services you'd like to have supported.
Release 4.2.0
Release 4.1.7
Bugfix: Need to use the union of generator uids present in verification stats and fool rate stats to set weights for new generative miners
Release 4.1.6
Improved generative miner time-to-incentive and score stability.
Release 4.1.4
Burn off, generator rewards on, discriminator WTA split into image and video winners