Skip to content

v431 release#2898

Merged
IntiTechnologies merged 44 commits into
mainfrom
error-descriptions
Jul 14, 2026
Merged

v431 release#2898
IntiTechnologies merged 44 commits into
mainfrom
error-descriptions

Conversation

@unarbos

@unarbos unarbos commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Three related pushes to make chain errors and subnet hyperparameters self-explanatory for users and agents:

Error descriptions (original scope)

  • New bittensor/error_descriptions.py: a researched, per-name description for all 355 classified chain errors — what raised it and where to check, grounded in the pallet source.
  • Surfaced everywhere: ChainError.description (and to_dict()) in the SDK, an extra note: in btcli failure diagnostics, btcli explain <Name>, the generated docs errors page, and /catalog/errors.json.
  • codegen.check --names gates completeness in both directions (undescribed / orphaned names fail CI).

Full owner-settable hyperparameter coverage

  • SetHyperparameter now covers every owner-settable AdminUtils call: adds adjustment_alpha, rho, max_burn, max_difficulty, bonds_penalty, alpha_sigmoid_steepness, min_childkey_take, owner_immune_neuron_limit, and alpha_low/alpha_high (the two-value sudo_set_alpha_values call: setting one side reads the current pair and preserves the other).
  • Fixed burn_increase_mult: U64F64 fixed-point now encodes correctly ({"bits": …}) and renders as ≈ 1.5 with a human --value 1.5 form (new fixed128 kind).
  • The hyperparameters listing now includes owner-settable values the runtime read omits (burn_half_life, yuma3_enabled, etc.) by querying their storage items, so the chart shows everything sudo set can change.

Per-hyperparameter docs

  • Every parameter carries a short blurb (≤38 chars, gated by codegen.check --units) shown as a dim description column in the CLI chart; names are OSC-8 hyperlinks and a see: footer links the docs.
  • 40 hand-written explainer pages under docs/hyperparameters/ (researched against the pallet source) plus 7 playable React demos: burn-cost simulator, difficulty controller, rho/kappa sigmoid, weight clipping + commit-reveal timeline, liquid-alpha curve, UID pruning grid, owner-cut split.
  • The overview index + nav meta are generated from the SDK registry; generate.py --check fails if a parameter lacks its explainer page.
  • Honest findings baked into the pages: the legacy interval burn controller is gone (adjustment_alpha/interval documented as inert), PoW registration is retired on the current runtime, and liquid alpha only applies when yuma3 is enabled.

Silent capping of locked alpha transfers removed

  • When locked alpha is transferred, and more than available is requested, the transaction will fail.
  • Previous behavior is to silently cap at the available amount.

Test plan

  • codegen.check --names / --coverage / --units all green
  • 949 SDK tests pass; ruff clean
  • generate.py --check green (docs and catalogs in sync, all 40 pages present)
  • Website tsc --noEmit and next build clean (all docs paths prerender)
  • Live-verified against mainnet netuid 120: full listing, single view, explain, and intent build for the new names

Add bittensor/error_descriptions.py: a researched description for each of
the 353 classified chain error names, grounded in the pallet raise sites.
Surface it in the docs errors table and /catalog/errors.json, in the btcli
failure diagnostic and `btcli explain <Name>`, and on ChainError for the
SDK. The --names gate now enforces that classification and descriptions
cover exactly the same names.

Co-authored-by: Cursor <cursoragent@cursor.com>
@vercel

vercel Bot commented Jul 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
subtensor Ready Ready Preview, Comment Jul 14, 2026 7:23pm

Request Review

@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

🛡️ AI Review — Skeptic (security review)

VERDICT: VULNERABLE

MEDIUM scrutiny: established contributor with repository write access; no Gittensor association found; error-descriptions -> main.

Static analysis found no evidence of malicious intent, and the trusted AI-review instruction files were not modified. Both previously reported runtime resource-exhaustion vulnerabilities remain present.

Findings

Sev File Finding
HIGH pallets/subtensor/src/macros/hooks.rs:185 Activated migrations perform unbounded upgrade work inline
HIGH pallets/subtensor/src/staking/remove_stake.rs:698 Dissolution deliberately bypasses the block weight limit inline

Prior-comment reconciliation

  • 6ade6442: not addressedmigrate_cleanup_swap_v3 remains activated with unbounded storage traversal, followed by another full identity-map scan.
  • a04e205b: not addressed — The dissolution code still explicitly finishes an entire hotkey prefix after the weight meter is exhausted instead of introducing a resumable inner cursor.

Conclusion

The PR remains vulnerable because runtime-upgrade and subnet-dissolution paths can perform attacker-influenced work beyond the block-weight budget. Both paths must be bounded and resumable before merge.


📜 Previous run (superseded)
Sev File Finding Status
HIGH pallets/subtensor/src/macros/hooks.rs:185 Activated migrations perform unbounded upgrade work ➡️ Carried forward to current findings
migrate_cleanup_swap_v3 remains activated with unbounded storage traversal, followed by another full identity-map scan.
HIGH pallets/subtensor/src/staking/remove_stake.rs:698 Dissolution deliberately bypasses the block weight limit ➡️ Carried forward to current findings
The dissolution code still explicitly finishes an entire hotkey prefix after the weight meter is exhausted instead of introducing a resumable inner cursor.

🔍 AI Review — Auditor (domain review)

VERDICT: 👎

Gittensor association UNKNOWN; established contributor with repository write access and substantial prior history, with release-sized changes receiving full scrutiny.

Description discrepancies

The PR body still omits substantial v431 runtime changes, including dissolution settlement, stake refunds and transfer semantics, alpha fee handling, migrations, proxy filtering, nonce weights, allocator support, and deployment tooling. The listed overlaps are incidental rather than duplicate implementations.

Findings

Sev File Finding
HIGH pallets/subtensor/src/migrations/migrate_clear_orphan_subnet_identities_v3.rs:43 Bound and fully account for the identity migration inline

Prior-comment reconciliation

  • ab791d57: not addressed — The reviewed diff still performs an unbounded scan and charges reads only from the number of removed entries.
  • 995fa9f7: no longer applies — The catalog shape change is documented as an intentional v431 breaking API change.

Conclusion

The identity migration remains an unbounded upgrade-time scan without try-runtime validation. It must be bounded or batched and fully weight-accounted before merge.


📜 Previous run (superseded)
Sev File Finding Status
HIGH pallets/subtensor/src/migrations/migrate_clear_orphan_subnet_identities_v3.rs:43 Bound and fully account for the identity migration ➡️ Carried forward to current findings
The reviewed diff still performs an unbounded scan and charges reads only from the number of removed entries.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AI review — see the sticky summary comment for the verdict and the inline comments below for specific findings.

Comment on lines +509 to +512
"chain_errors": {
n: {"code": c.value, "description": error_descriptions.DESCRIPTIONS[n]}
for n, c in sorted(error_map.NAME_TO_CODE.items())
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[MEDIUM] Preserve the published catalog's value type

/catalog/errors.json is documented as a machine-readable agent API, but this changes every chain_errors[name] value from a string to an object. Existing consumers comparing or decoding that value as an error-code string will break without any endpoint/version change. Keep the established mapping and publish descriptions in a parallel field.

Suggested change
"chain_errors": {
n: {"code": c.value, "description": error_descriptions.DESCRIPTIONS[n]}
for n, c in sorted(error_map.NAME_TO_CODE.items())
},
"chain_errors": {n: c.value for n, c in sorted(error_map.NAME_TO_CODE.items())},
"chain_error_descriptions": {
n: error_descriptions.DESCRIPTIONS[n]
for n in sorted(error_map.NAME_TO_CODE)
},

@github-actions

Copy link
Copy Markdown
Contributor

🔄 AI review updated — Skeptic: SAFE Auditor: 👍

The move from gh-action-pypi-publish to uv publish silently dropped
attestation generation: uv only uploads attestations that already exist
in dist/. Generate them with astral-sh/attest-action in each publish job
(stable, rc, and dev channels) so releases are provenance-signed again.

Co-authored-by: Cursor <cursoragent@cursor.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AI review — see the sticky summary comment for the verdict and the inline comments below for specific findings.

Comment on lines +509 to +512
"chain_errors": {
n: {"code": c.value, "description": error_descriptions.DESCRIPTIONS[n]}
for n, c in sorted(error_map.NAME_TO_CODE.items())
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[MEDIUM] Preserve the published catalog's value type

This still changes every chain_errors[name] value from a string to an object, breaking existing consumers of this public machine-readable catalog. Preserve the established map and publish descriptions under a new sibling key.

Suggested change
"chain_errors": {
n: {"code": c.value, "description": error_descriptions.DESCRIPTIONS[n]}
for n, c in sorted(error_map.NAME_TO_CODE.items())
},
"chain_errors": {n: c.value for n, c in sorted(error_map.NAME_TO_CODE.items())},
"chain_error_descriptions": {
n: error_descriptions.DESCRIPTIONS[n]
for n in sorted(error_map.NAME_TO_CODE)
},

@github-actions

Copy link
Copy Markdown
Contributor

🔄 AI review updated — Skeptic: SAFE Auditor: 👍

unarbos and others added 2 commits July 13, 2026 16:32
…isting

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@unarbos unarbos changed the title Describe every chain error with its trigger and where to check Describe every chain error and give every hyperparameter full coverage, blurbs, and explainer pages Jul 13, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AI review — see the sticky summary comment for the verdict and the inline comments below for specific findings.

Comment on lines +567 to +570
"chain_errors": {
n: {"code": c.value, "description": error_descriptions.DESCRIPTIONS[n]}
for n, c in sorted(error_map.NAME_TO_CODE.items())
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[MEDIUM] Preserve the published catalog's value type

chain_errors is an already-published catalog API whose values were strings. Replacing every value with an object silently breaks consumers expecting chain_errors[name] to be an error-code string. Preserve that field and publish descriptions under a new sibling key.

Suggested change
"chain_errors": {
n: {"code": c.value, "description": error_descriptions.DESCRIPTIONS[n]}
for n, c in sorted(error_map.NAME_TO_CODE.items())
},
"chain_errors": {n: c.value for n, c in sorted(error_map.NAME_TO_CODE.items())},
"chain_error_descriptions": {
n: error_descriptions.DESCRIPTIONS[n]
for n in sorted(error_map.NAME_TO_CODE)
},

@github-actions

Copy link
Copy Markdown
Contributor

🔄 AI review updated — Skeptic: SAFE Auditor: 👍

Every page that previously had no visual gets a dedicated interactive
chart (16 new components: epoch timeline, activity bars, permit line,
rate-limit timelines, version/registration gates, per-block caps,
bonds clipping/reset, Yuma3 comparison, serve-rate, childkey range,
transfer flow, legacy adjustment, PoW dead-end). Family demos now
adapt scenarios, sliders, and captions to their focus parameter.

Co-authored-by: Cursor <cursoragent@cursor.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AI review — see the sticky summary comment for the verdict and the inline comments below for specific findings.

Comment on lines +567 to +570
"chain_errors": {
n: {"code": c.value, "description": error_descriptions.DESCRIPTIONS[n]}
for n, c in sorted(error_map.NAME_TO_CODE.items())
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[MEDIUM] Preserve the published catalog's value type

chain_errors is already a published catalog whose values are strings. Replacing every value with an object breaks consumers that deserialize or compare the existing shape. Preserve that field and publish descriptions separately.

Suggested change
"chain_errors": {
n: {"code": c.value, "description": error_descriptions.DESCRIPTIONS[n]}
for n, c in sorted(error_map.NAME_TO_CODE.items())
},
"chain_errors": {n: c.value for n, c in sorted(error_map.NAME_TO_CODE.items())},
"chain_error_descriptions": {
n: error_descriptions.DESCRIPTIONS[n]
for n in sorted(error_map.NAME_TO_CODE)
},

@github-actions

Copy link
Copy Markdown
Contributor

🔄 AI review updated — Skeptic: SAFE Auditor: 👍

…, and split error docs into per-error pages

Co-authored-by: Cursor <cursoragent@cursor.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AI review — see the sticky summary comment for the verdict and the inline comments below for specific findings.

/// (rate-limited via `OwnerHyperparamUpdate`, respects the admin freeze
/// window) or the root account (bypasses both). This supersedes the
/// absolute-blocks `sudo_set_activity_cutoff`.
#[pallet::call_index(97)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[CRITICAL] Bump spec_version for runtime changes

This adds a runtime dispatchable and the PR also moves/removes calls and changes proxy filters, but runtime/src/lib.rs remains at spec_version: 430. Substrate uses this version to decide native-runtime compatibility; deploying changed Wasm under the same version can leave older nodes executing their old native implementation while other nodes execute the new Wasm, creating a consensus-fork risk. Increment spec_version in this PR.

@github-actions

Copy link
Copy Markdown
Contributor

🔄 AI review updated — Skeptic: VULNERABLE

Co-authored-by: Cursor <cursoragent@cursor.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AI review — see the sticky summary comment for the verdict and the inline comments below for specific findings.

/// (rate-limited via `OwnerHyperparamUpdate`, respects the admin freeze
/// window) or the root account (bypasses both). This supersedes the
/// absolute-blocks `sudo_set_activity_cutoff`.
#[pallet::call_index(97)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[CRITICAL] Bump spec_version for runtime changes

This PR adds and relocates runtime calls, changes dispatch semantics, weights, proxy filters, and metadata, but runtime/src/lib.rs still has spec_version: 430, identical to the base. Nodes may treat the changed runtime as native-compatible with the old implementation, risking native/Wasm execution divergence. Increment spec_version for this runtime upgrade.

@github-actions

Copy link
Copy Markdown
Contributor

🔄 AI review updated — Skeptic: VULNERABLE

unarbos and others added 2 commits July 14, 2026 11:02
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Clone last_key before iter_from so pallet-subtensor builds again, drop the e2e manifest expectation to 112 after removing set_mechanism_emission_split, and ruff-format query.py.

Co-authored-by: Cursor <cursoragent@cursor.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AI review — see the sticky summary comment for the verdict and the inline comments below for specific findings.

Comment on lines +183 to +185
// Fold deprecated SubnetTaoProvided / SubnetAlphaInProvided residuals into the
// main AMM reserves (issue #2793). Guarded by HasMigrationRun, so it only runs once.
.saturating_add(migrations::migrate_cleanup_swap_v3::migrate_cleanup_swap_v3::<T>())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[HIGH] Activated migrations perform unbounded upgrade work

migrate_cleanup_swap_v3 traverses both deprecated storage maps and clears their prefixes in one runtime upgrade, while the newly chained identity cleanup also collects every SubnetIdentitiesV3 key before returning. Their returned weights merely report work after it has occurred; they do not constrain execution to the upgrade's block budget. An attacker-inflated map can therefore make on_runtime_upgrade exceed the maximum block weight. Convert both migrations to bounded, cursor-based multi-block migrations (or prove and enforce a strict storage bound) and account every read/write.

Comment on lines 692 to +698
}
hotkeys_in_subnet.push(hot.clone());

let mut inner_read_all = true;
let mut coldkey_value_vec: Vec<(T::AccountId, u128)> = Vec::new();

// Handle one hotkey and all its coldkeys or skip the hotkey if the weight is not enough
// Then we just need to record the hotkey as checkpoint
// Drain the whole hotkey prefix once started. Weight is accounted when it
// still fits; overshoot is allowed so the cursor can advance past this hotkey.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[HIGH] Dissolution deliberately bypasses the block weight limit

Once a hotkey prefix starts, this loop continues scanning even after weight_meter is exhausted; the same pattern is added to the total-calculation and cleanup phases. A hotkey can have an attacker-influenced number of coldkey/subnet entries, so one on_idle call can perform unbounded reads/writes beyond its remaining weight and stall block production. Progress must use a two-dimensional cursor (hotkey plus inner storage key) so processing can stop exactly at the budget and resume next block.

@github-actions

Copy link
Copy Markdown
Contributor

🔄 AI review updated — Skeptic: VULNERABLE

Removing the unused SetMechanismEmissionSplit intent left AdminUtils.sudo_set_mechanism_emission_split unwrapped; list it as raw-only so SDK offline checks pass.

Co-authored-by: Cursor <cursoragent@cursor.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AI review — see the sticky summary comment for the verdict and the inline comments below for specific findings.

.saturating_add(migrations::migrate_associated_evm_address_index::migrate_associated_evm_address_index::<T>())
// Fold deprecated SubnetTaoProvided / SubnetAlphaInProvided residuals into the
// main AMM reserves (issue #2793). Guarded by HasMigrationRun, so it only runs once.
.saturating_add(migrations::migrate_cleanup_swap_v3::migrate_cleanup_swap_v3::<T>())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[HIGH] Activated migrations perform unbounded upgrade work

This activates migrate_cleanup_swap_v3 during on_runtime_upgrade; it fully iterates and clears two storage maps. The newly chained identity migration also collects every SubnetIdentitiesV3 key before returning. Reporting weight after these unbounded traversals does not constrain execution, so attacker-influenced state size can make the upgrade exceed the block budget. Convert these migrations to bounded, resumable steps with persistent cursors before activating them.

// Handle one hotkey and all its coldkeys or skip the hotkey if the weight is not enough
// Then we just need to record the hotkey as checkpoint
// Drain the whole hotkey prefix once started. Weight is accounted when it
// still fits; overshoot is allowed so the cursor can advance past this hotkey.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[HIGH] Dissolution deliberately bypasses the block weight limit

Once a hotkey is started, this path deliberately continues its complete, attacker-influenced coldkey prefix after WeightMeter is exhausted. The same pattern appears in total calculation and cleanup, including transfers and storage writes that are performed without consuming their weight. A hotkey with a sufficiently large prefix can therefore make on_idle exceed the block budget. Use a resumable inner-prefix cursor instead of allowing overshoot.

@github-actions

Copy link
Copy Markdown
Contributor

🔄 AI review updated — Skeptic: VULNERABLE

Co-authored-by: Cursor <cursoragent@cursor.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AI review — see the sticky summary comment for the verdict and the inline comments below for specific findings.

Comment on lines +182 to +185
.saturating_add(migrations::migrate_associated_evm_address_index::migrate_associated_evm_address_index::<T>())
// Fold deprecated SubnetTaoProvided / SubnetAlphaInProvided residuals into the
// main AMM reserves (issue #2793). Guarded by HasMigrationRun, so it only runs once.
.saturating_add(migrations::migrate_cleanup_swap_v3::migrate_cleanup_swap_v3::<T>())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[HIGH] Activated migrations perform unbounded upgrade work

This activates migrate_cleanup_swap_v3, which traverses storage maps without a per-block bound, and then chains another migration that scans every SubnetIdentitiesV3 key. Returning the accumulated weight after completing the work does not constrain execution to the maximum block weight; sufficiently large attacker-influenced state can make the runtime upgrade exceed the block budget and stall chain progress. Convert these migrations to bounded, cursor-based multi-block migrations before activation.

Comment on lines 694 to +698

let mut inner_read_all = true;
let mut coldkey_value_vec: Vec<(T::AccountId, u128)> = Vec::new();

// Handle one hotkey and all its coldkeys or skip the hotkey if the weight is not enough
// Then we just need to record the hotkey as checkpoint
// Drain the whole hotkey prefix once started. Weight is accounted when it
// still fits; overshoot is allowed so the cursor can advance past this hotkey.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[HIGH] Dissolution deliberately bypasses the block weight limit

Once the meter is exhausted, this loop deliberately continues scanning the complete hotkey prefix. A hotkey can have attacker-influenced stake entries across many coldkeys and subnets, so one on_idle invocation can perform unbounded reads and later writes beyond its remaining weight, potentially making the block non-viable. Persist a secondary cursor for the inner prefix and resume it in a later block instead of overshooting the meter.

@github-actions

Copy link
Copy Markdown
Contributor

🔄 AI review updated — Skeptic: VULNERABLE

unarbos and others added 2 commits July 14, 2026 12:47
…ndices

fix(runtime): preserve deprecated tempo call indices
Reject bool values other than 0/1, negative human rao/fixed128, and
integers outside each parameter's bit width before composing the call.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AI review — see the sticky summary comment for the verdict and the inline comments below for specific findings.

.saturating_add(migrations::migrate_associated_evm_address_index::migrate_associated_evm_address_index::<T>())
// Fold deprecated SubnetTaoProvided / SubnetAlphaInProvided residuals into the
// main AMM reserves (issue #2793). Guarded by HasMigrationRun, so it only runs once.
.saturating_add(migrations::migrate_cleanup_swap_v3::migrate_cleanup_swap_v3::<T>())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[HIGH] Activated migrations perform unbounded upgrade work

This activates migrate_cleanup_swap_v3, which fully iterates two storage maps and then clears both prefixes in one runtime upgrade. It is immediately followed by migrate_clear_orphan_subnet_identities_v3, which collects and scans the entire identity map. Returning the accumulated weight after completing the work does not impose a bound; attacker-influenced storage cardinality can make the upgrade block exceed its execution budget. Convert these migrations to bounded, cursor-based work performed across blocks.

// Handle one hotkey and all its coldkeys or skip the hotkey if the weight is not enough
// Then we just need to record the hotkey as checkpoint
// Drain the whole hotkey prefix once started. Weight is accounted when it
// still fits; overshoot is allowed so the cursor can advance past this hotkey.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[HIGH] Dissolution deliberately bypasses the block weight limit

Once a hotkey scan begins, this path continues reading its entire Alpha/AlphaV2 prefix after WeightMeter is exhausted; the settlement and cleanup paths similarly continue writes over budget. A hotkey can have attacker-influenced entries across subnets, so one on_idle invocation can exceed the block budget and stall block production. The inner scan needs its own resumable cursor instead of deliberately overshooting weight.

@github-actions

Copy link
Copy Markdown
Contributor

🔄 AI review updated — Skeptic: VULNERABLE

Co-authored-by: Cursor <cursoragent@cursor.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AI review — see the sticky summary comment for the verdict and the inline comments below for specific findings.

Comment on lines +183 to +185
// Fold deprecated SubnetTaoProvided / SubnetAlphaInProvided residuals into the
// main AMM reserves (issue #2793). Guarded by HasMigrationRun, so it only runs once.
.saturating_add(migrations::migrate_cleanup_swap_v3::migrate_cleanup_swap_v3::<T>())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[HIGH] Activated migrations perform unbounded upgrade work

These newly activated migrations traverse entire storage maps during on_runtime_upgrade: migrate_cleanup_swap_v3 iterates and removes both deprecated reserve maps, while the following identity migration scans every SubnetIdentitiesV3 key. Returning the measured weight only accounts for work after it has executed; it does not constrain execution to the block budget. Attacker-influenced map growth can therefore make the upgrade block exceed its weight or proof-size limits. Convert these migrations to bounded, resumable processing with persistent cursors before activation.

Comment on lines 696 to +698

// Handle one hotkey and all its coldkeys or skip the hotkey if the weight is not enough
// Then we just need to record the hotkey as checkpoint
// Drain the whole hotkey prefix once started. Weight is accounted when it
// still fits; overshoot is allowed so the cursor can advance past this hotkey.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[HIGH] Dissolution deliberately bypasses the block weight limit

The implementation intentionally drains an entire hotkey prefix after WeightMeter is exhausted. A hotkey can have attacker-influenced stake entries, so one on_idle invocation can perform arbitrarily more reads, writes, and transfers than its supplied limit. The same overshoot pattern also appears in the total-value and cleanup phases. Preserve a resumable inner cursor (hotkey, coldkey/storage key) instead, so every phase stops before exceeding the meter and continues next block.

@github-actions

Copy link
Copy Markdown
Contributor

🔄 AI review updated — Skeptic: VULNERABLE

@IntiTechnologies IntiTechnologies merged commit 32f3b65 into main Jul 14, 2026
175 of 176 checks passed
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.

4 participants