BlocksSinceLastStep

Storage Map v101 → v472 · 1 shape change

Blocks since the last successful epoch run, capped at tempo + 1.

Explore chain
Queried by: validatorsminersdevelopersanalytics

The 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

v424 v432 Breaking

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

#NameTypeDescription
1
netuid
NetUid u16netuid (NetUid, hashed key component)

Stored Value

blocks_since_last_step (u64)

RAO -> TAO (/ 10^9)

Default Value

0 0x0000000000000000

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

Write Source Runtime Hook

Modified by runtime hooks (e.g., epoch transitions), not directly by user extrinsics

As of block 7,429,232

Version History

v101 block 1 Added
v425–v431 · runtime versions skipped on chain (never deployed)
v432 block 8,636,190 Shape changed Current
  • key : u16 → NetUid

Runtime Info

View Source
Pallet
SubtensorModule
Storage Kind
Map
First Version
v101
Current Version
v472