[Feature] Configurable Tempo & Owner-Triggered Epochs#2638
Conversation
| /// stateful scheduler (period `tempo + 1`, anchored on `LastEpochBlock`). Used by the | ||
| /// admin-freeze-window predicate and external tooling. Returns `u64::MAX` when | ||
| /// `tempo == 0` (legacy defensive short-circuit). | ||
| pub fn blocks_until_next_epoch(netuid: NetUid, tempo: u16, block_number: u64) -> u64 { |
There was a problem hiding this comment.
"Period is tempo + 1: next firing at last + tempo + 1." comment and the code below is not anymore correct.
There was a problem hiding this comment.
Updated the function name and the comment to avoid the confusion: ad7ba80
|
Hey, @basfroman , we have a bunch of tests that failed on the SDK side. |
# Conflicts: # pallets/subtensor/src/utils/rate_limiting.rs
# Conflicts: # pallets/subtensor/src/coinbase/run_coinbase.rs # pallets/subtensor/src/macros/dispatches.rs # pallets/subtensor/src/macros/events.rs # pallets/subtensor/src/macros/hooks.rs # pallets/subtensor/src/tests/migration.rs # pallets/subtensor/src/tests/mod.rs # pallets/subtensor/src/utils/rate_limiting.rs
- Updated reveal/commit logic based on stateful epoch index - Updated migration for CR-v2 storage item
| // `(hash, commit_epoch, commit_block, _)` layout. Field 1 was the absolute | ||
| // `commit_block`; it becomes `commit_epoch` (legacy modulo epoch). Field 2 | ||
| // keeps the absolute `commit_block` (used by the epoch column-mask). | ||
| let crv2_entries: Vec<_> = WeightCommits::<T>::iter().collect(); |
There was a problem hiding this comment.
[HIGH] Migration collects unbounded commit storage into Wasm memory
WeightCommits is live user-controlled commit-reveal storage, and this runtime upgrade collects the entire double map into a Vec before rewriting any entries. A large enough live commit set can make the upgrade allocate excessive Wasm memory or exceed block limits before the migration can complete, which is a chain-upgrade safety risk. Rewrite this as a bounded/streaming migration, for example with translate/paged cursor state or by otherwise bounding the number of entries processed per upgrade step.
|
🔄 AI review updated — Skeptic: VULNERABLE |
| // `(hash, commit_epoch, commit_block, _)` layout. Field 1 was the absolute | ||
| // `commit_block`; it becomes `commit_epoch` (legacy modulo epoch). Field 2 | ||
| // keeps the absolute `commit_block` (used by the epoch column-mask). | ||
| let crv2_entries: Vec<_> = WeightCommits::<T>::iter().collect(); |
There was a problem hiding this comment.
[HIGH] Migration collects unbounded commit storage into Wasm memory
This runtime migration still calls WeightCommits::<T>::iter().collect() before rewriting entries. WeightCommits is live user-controlled commit-reveal storage, so a sufficiently large set of entries can force the runtime upgrade to allocate an unbounded vector in Wasm memory and risk trapping/bricking the chain during on_runtime_upgrade. Rewrite this as a bounded/streaming migration pattern, or split it behind a bounded cursor/kill-and-reinsert strategy whose per-block weight and memory are capped.
|
🔄 AI review updated — Skeptic: VULNERABLE |
# Conflicts: # pallets/subtensor/src/macros/hooks.rs
| // `(hash, commit_epoch, commit_block, _)` layout. Field 1 was the absolute | ||
| // `commit_block`; it becomes `commit_epoch` (legacy modulo epoch). Field 2 | ||
| // keeps the absolute `commit_block` (used by the epoch column-mask). | ||
| let crv2_entries: Vec<_> = WeightCommits::<T>::iter().collect(); |
There was a problem hiding this comment.
[HIGH] Migration collects unbounded commit storage into Wasm memory
WeightCommits::<T>::iter().collect() materializes every CR-v2 commit queue in the runtime Wasm heap during on_runtime_upgrade. This storage is user-populated and can grow across all subnets/accounts, so a large live state can make the upgrade exceed memory/weight and abort or brick the chain before the rewrite completes. Iterate and translate entries incrementally, or use a bounded/multi-block migration with progress state instead of collecting the full map first.
|
🔄 AI review updated — Skeptic: VULNERABLE |
| // `(hash, commit_epoch, commit_block, _)` layout. Field 1 was the absolute | ||
| // `commit_block`; it becomes `commit_epoch` (legacy modulo epoch). Field 2 | ||
| // keeps the absolute `commit_block` (used by the epoch column-mask). | ||
| let crv2_entries: Vec<_> = WeightCommits::<T>::iter().collect(); |
There was a problem hiding this comment.
[HIGH] Migration collects unbounded commit storage into Wasm memory
This runtime migration still loads every WeightCommits entry into a single Vec before rewriting them. WeightCommits is live user-controlled commit-reveal storage across subnet/mechanism/hotkey keys, so a sufficiently large state can make the upgrade allocate excessive Wasm memory or exceed the block during on_runtime_upgrade, risking a failed runtime upgrade. Rewrite this as a bounded/streaming migration pattern that does not materialize the entire map at once, with explicit weight accounting for the processed entries.
|
🔄 AI review updated — Skeptic: VULNERABLE |
| // `(hash, commit_epoch, commit_block, _)` layout. Field 1 was the absolute | ||
| // `commit_block`; it becomes `commit_epoch` (legacy modulo epoch). Field 2 | ||
| // keeps the absolute `commit_block` (used by the epoch column-mask). | ||
| let crv2_entries: Vec<_> = WeightCommits::<T>::iter().collect(); |
There was a problem hiding this comment.
[HIGH] Migration collects unbounded commit storage into Wasm memory
WeightCommits::<T>::iter().collect() materializes every live CR-v2 commit entry into a Vec during on_runtime_upgrade. This storage is user-growth dependent, so a large live commit set can exhaust runtime Wasm memory or make the upgrade block fail before any bounded rewrite happens. Iterate and rewrite entries without collecting the full map first, or split the migration into a bounded cursor-based upgrade path with explicit per-block limits.
|
🔄 AI review updated — Skeptic: VULNERABLE |
# Conflicts: # pallets/subtensor/src/benchmarks.rs # pallets/subtensor/src/weights.rs
| // `(hash, commit_epoch, commit_block, _)` layout. Field 1 was the absolute | ||
| // `commit_block`; it becomes `commit_epoch` (legacy modulo epoch). Field 2 | ||
| // keeps the absolute `commit_block` (used by the epoch column-mask). | ||
| let crv2_entries: Vec<_> = WeightCommits::<T>::iter().collect(); |
There was a problem hiding this comment.
[HIGH] Migration collects unbounded commit storage into Wasm memory
WeightCommits::<T>::iter().collect() materializes every live CR-v2 commit queue into Wasm memory during the runtime upgrade. WeightCommits is user-populated storage, so a sufficiently large live state can exceed the runtime memory/weight budget and abort the upgrade. Rewrite this as a streaming/key-by-key migration, or otherwise bound the number of entries processed per upgrade path, and account for each processed DB read/write without holding all entries at once.
|
🔄 AI review updated — Skeptic: VULNERABLE |
| // `(hash, commit_epoch, commit_block, _)` layout. Field 1 was the absolute | ||
| // `commit_block`; it becomes `commit_epoch` (legacy modulo epoch). Field 2 | ||
| // keeps the absolute `commit_block` (used by the epoch column-mask). | ||
| let crv2_entries: Vec<_> = WeightCommits::<T>::iter().collect(); |
There was a problem hiding this comment.
[HIGH] Migration collects unbounded commit storage into Wasm memory
WeightCommits::<T>::iter().collect() materializes every live CR-v2 commit entry before rewriting any of them during on_runtime_upgrade. This storage is user-populated across hotkeys/subnets, so a large live set can exhaust Wasm memory or make the runtime upgrade block fail before the migration writes progress. Rewrite entries in a streaming/draining pass, or bound/chunk the migration so each block has a hard upper limit and durable progress state.
|
🔄 AI review updated — Skeptic: VULNERABLE |
| // `(hash, commit_epoch, commit_block, _)` layout. Field 1 was the absolute | ||
| // `commit_block`; it becomes `commit_epoch` (legacy modulo epoch). Field 2 | ||
| // keeps the absolute `commit_block` (used by the epoch column-mask). | ||
| let crv2_entries: Vec<_> = WeightCommits::<T>::iter().collect(); |
There was a problem hiding this comment.
[HIGH] Migration collects unbounded commit storage into Wasm memory
This runtime migration executes during upgrade, and WeightCommits::<T>::iter().collect() loads every CR-v2 commit entry, including each account's commit queue, into one Wasm Vec before rewriting. That storage is live chain state and this migration has no batching/cursor bound, so a sufficiently large state can exhaust Wasm memory or make the upgrade overweight and brick the chain. Rewrite this as a bounded/streaming storage translation, or split it into cursor-based migration steps with version state.
|
🔄 AI review updated — Skeptic: VULNERABLE |
Description
Implementation of the specification.
Demo (non-technical): https://dynamic-tempo-non-technical.netlify.app/
Demo (technical): https://dynamic-tempo-technical.netlify.app/#1
Related Issue(s)
Type of Change
Breaking Change
Check the 11. Breaking changes & compatibility section.
Checklist
./scripts/fix_rust.shto ensure my code is formatted and linted correctlyScreenshots (if applicable)
Please include any relevant screenshots or GIFs that demonstrate the changes made.
Additional Notes
Please provide any additional information or context that may be helpful for reviewers.
Simulations (miner rewards Events):
Trigger epoch:


Set tempo: