BlocksSinceLastStep
Storage Map v101 → v472 · 1 shape changeBlocks since the last successful epoch run, capped at tempo + 1.
Explore chainThe Big Picture
How long since this subnet last distributed. The scheduler anchors on LastEpochBlock; this counter exists so that a subnet whose slots keep failing still gets an epoch once it passes tempo.
Why This Matters
A healthy subnet cycles this between 0 and tempo. A value pinned at tempo + 1 means epochs are being skipped (EpochSkipped) or deferred (EpochDeferred) and pending emission is accumulating.
Example Scenario
Query BlocksSinceLastStep(netuid=1) returns 75 with Tempo 100. The last epoch ran 75 blocks ago. The next slot is at LastEpochBlock + 100 unless PendingEpochAt is set earlier.
Common Questions
- Why does this matter for validators?
- Weight updates close to epoch end count toward that epoch. Updates right after a step have more time to affect miner behavior before evaluation.
- Is the next step exactly Tempo - BlocksSinceLastStep blocks away?
- Not necessarily. Read LastEpochBlock + Tempo and PendingEpochAt for the schedule. This counter only forces a run when it exceeds tempo, and it stops counting at tempo + 1.
Use Cases
- Detect subnets whose epoch is being skipped or deferred (counter stuck at tempo + 1)
- Calculate progress through the current epoch
- Build emission timing dashboards
- Plan weight updates relative to epoch
From Chain Metadata
MAP ( netuid ) --> blocks_since_last_step
Purpose & Usage
Purpose
Count blocks since the subnet last ran its epoch, and act as the safety net that forces a run when the counter exceeds tempo. Since v423 the primary schedule is LastEpochBlock + Tempo.
Common Query Patterns
- Query by netuid
- Build epoch countdown displays
- Monitor emission timing
Notes
- drain_pending (run_coinbase.rs L394-L401 at da06f033, v452) increments it every block with min(tempo + 1), and resets it to 0 only when an epoch actually runs (L423). A skipped or deferred slot leaves it at the cap.
- should_run_epoch_with_tempo fires when PendingEpochAt is due, or this counter exceeds tempo, or current_block − LastEpochBlock ≥ tempo. Use LastEpochBlock + Tempo for the countdown; this counter is the fallback path.
Migration Notes
key type changed from u16 to NetUid
Update decoders: SCALE encoding is positional, so any signature change (added, removed, or type-changed fields, or storage shape changes) shifts byte offsets and existing decoders will misparse this item. Re-derive types from the new metadata.
Query Keys
| # | Name | Type | Description |
|---|---|---|---|
| 1 | netuid | NetUid u16 | netuid (NetUid, hashed key component) |
Stored Value
blocks_since_last_step (u64)
RAO -> TAO (/ 10^9)
Default Value
Absent keys read as this value — querying a key that has never been written returns this default, not an error or empty result.
Code Examples
import { createClient, Binary } from "polkadot-api";
import { getWsProvider } from "polkadot-api/ws";
import { sub } from "@polkadot-api/descriptors"; // generated by: npx papi add sub -w wss://entrypoint-finney.opentensor.ai:443
const client = createClient(getWsProvider("wss://entrypoint-finney.opentensor.ai:443"));
const api = client.getTypedApi(sub);
// Query BlocksSinceLastStep storage
const netuid = 1;
const result = await api.query.SubtensorModule.BlocksSinceLastStep.getValue(netuid);
console.log("BlocksSinceLastStep:", result);On-Chain Activity
Modified by runtime hooks (e.g., epoch transitions), not directly by user extrinsics
As of block 7,429,232
Version History
- key :
u16→NetUid
Runtime Info
View Source- Pallet
- SubtensorModule
- Storage Kind
- Map
- First Version
- v101
- Current Version
- v472