Skip to content

[Feature] Configurable Tempo & Owner-Triggered Epochs#2638

Open
evgeny-s wants to merge 55 commits into
devnet-readyfrom
feature/dynamic-tempo
Open

[Feature] Configurable Tempo & Owner-Triggered Epochs#2638
evgeny-s wants to merge 55 commits into
devnet-readyfrom
feature/dynamic-tempo

Conversation

@evgeny-s

@evgeny-s evgeny-s commented May 6, 2026

Copy link
Copy Markdown
Collaborator

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)

  • Closes #[issue number]

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Other (please describe):

Breaking Change

Check the 11. Breaking changes & compatibility section.

Checklist

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have run ./scripts/fix_rust.sh to ensure my code is formatted and linted correctly
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Screenshots (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:
image
Set tempo:
image

@evgeny-s evgeny-s added the skip-cargo-audit This PR fails cargo audit but needs to be merged anyway label May 6, 2026
@evgeny-s evgeny-s changed the title Dynamic tempo implementation [Feature] Configurable Tempo & Owner-Triggered Epochs May 6, 2026
Comment thread pallets/subtensor/src/coinbase/run_coinbase.rs
/// 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 {

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.

"Period is tempo + 1: next firing at last + tempo + 1." comment and the code below is not anymore correct.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Updated the function name and the comment to avoid the confusion: ad7ba80

Comment thread pallets/subtensor/src/coinbase/tempo_control.rs Outdated
@evgeny-s

Copy link
Copy Markdown
Collaborator Author

Hey, @basfroman , we have a bunch of tests that failed on the SDK side.
The main reason is now we have a stateful calculation for the next epoch block, because the tempo is dynamic.
So, here you can just call the new runtime API - get_next_epoch_start_block.

Comment thread pallets/subtensor/src/utils/misc.rs
evgeny-s added 3 commits May 15, 2026 14:07
# 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
@github-actions github-actions Bot mentioned this pull request May 21, 2026
4 tasks
- Updated reveal/commit logic based on stateful epoch index
- Updated migration for CR-v2 storage item

@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.

// `(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();

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] 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.

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

🔄 AI review updated — Skeptic: VULNERABLE

@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.

// `(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();

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] 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.

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

🔄 AI review updated — Skeptic: VULNERABLE

@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.

// `(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();

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] 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.

@github-actions

Copy link
Copy Markdown
Contributor

🔄 AI review updated — Skeptic: VULNERABLE

@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.

// `(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();

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] 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.

@github-actions

Copy link
Copy Markdown
Contributor

🔄 AI review updated — Skeptic: VULNERABLE

@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.

// `(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();

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] 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.

@github-actions

Copy link
Copy Markdown
Contributor

🔄 AI review updated — Skeptic: VULNERABLE

# Conflicts:
#	pallets/subtensor/src/benchmarks.rs
#	pallets/subtensor/src/weights.rs

@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.

// `(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();

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] 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.

@github-actions

Copy link
Copy Markdown
Contributor

🔄 AI review updated — Skeptic: VULNERABLE

@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.

// `(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();

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] 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.

@github-actions

Copy link
Copy Markdown
Contributor

🔄 AI review updated — Skeptic: VULNERABLE

@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.

// `(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();

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] 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.

@github-actions

Copy link
Copy Markdown
Contributor

🔄 AI review updated — Skeptic: VULNERABLE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-change This PR introduces a noteworthy breaking change skip-cargo-audit This PR fails cargo audit but needs to be merged anyway

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants