Logo
Nuevos usuarios de RPC: 35% de descuento el primer mesVer oferta
OnFinality Learn
Guías de red y protocolo13 min de lectura

Tempo, épocas y emisión de subredes de Bittensor vía RPC

Lee el reloj de una subred de Bittensor —tempo, índice de época, bloques desde el último paso y emisión— directamente desde un nodo Subtensor mediante JSON-RPC.

TL;DR

El reloj de una subred de Bittensor no es un temporizador de reloj de pared: una cadena Subtensor avanza una única altura de bloque global, mientras que cada subred cuenta de forma independiente los bloques desde su último paso de tempo. Cuando ese contador alcanza el hiperparámetro tempo de la subred, el índice de época de la subred se incrementa y la emisión alfa se distribuye al pool de emisiones de esa subred. Leer esto vía RPC implica separar tres relojes: la altura de bloque de la cadena, el contador de tempo por subred y el índice de época, y cualquier estimación de reloj de pared derivada de ellos. La ruta de lectura concreta es Substrate JSON-RPC state_getStorage con una clave twox128(twox128(prefix) + twox128(item) + blake2_128_concat(map_key) opcional), más chain_getHeader para la altura de bloque. Esta guía muestra los accesores tipados de @polkadot/api, un ejemplo ejecutable en Node.js, un método de muestreo de tiempo de bloque y un manual de solución de problemas para retornos nulos, netuids incorrectos, fallos de decodificación SCALE y almacenamiento histórico podado.

El reloj de la subred de Bittensor: el tempo como intervalo de bloques, no como duración

La cadena Subtensor de Bittensor avanza un único número de bloque de cadena global en cada bloque. De forma independiente, cada subred cuenta los bloques desde su propio último paso de tempo. Cuando ese contador por subred alcanza el hiperparámetro tempo de la subred, el índice de época de la subred se incrementa y la emisión alfa se distribuye al pool de emisiones de esa subred. La documentación de Bittensor describe el tempo como un hiperparámetro por subred medido en bloques, y una época como el intervalo entre dos pasos de tempo; el código fuente del pallet Subtensor (opentensor/subtensor, pallets/subtensor/src/lib.rs) es la fuente de verdad para los elementos de almacenamiento SubnetEmission y Epoch que mantienen este estado.

Debido a que el tempo es un intervalo de bloques, la duración en tiempo real de una época no es fija. Si el tiempo de bloque varía, el mismo valor de tempo produce diferentes duraciones de época en el mundo real. Esta es la distinción más importante para cualquiera que construya paneles, alertas o contabilidad de emisiones sobre Subtensor: estás leyendo una máquina que cuenta bloques, y cualquier valor en segundos que muestres es una estimación derivada.

Los tres relojes que un lector debe mantener separados son: (1) la altura de bloque de la cadena —un reloj global para toda la cadena; (2) el contador de tempo y el índice de época —un reloj por subred, indexado por netuid; y (3) cualquier estimación de reloj de pared derivada de los dos anteriores. Confundirlos es el error operativo más común, y normalmente se manifiesta como una cuenta regresiva de época que se desvía, o una proyección de emisión que asume un tiempo de bloque constante.

  • Altura de bloque de la cadena: global, monotónica, se lee vía chain_getHeader.
  • Contador de tempo e índice de época: por subred, indexado por netuid, se lee del almacenamiento del pallet Subtensor.
  • Estimación de reloj de pared: derivada, retrospectiva, y solo tan buena como el tiempo de bloque muestreado.
  • El tempo está documentado / varía por subred — no codifiques un único valor constante global.

Avance de época y división del pool de emisiones

Cuando el contador de tempo de una subred alcanza su tempo, el índice de época se incrementa y la emisión alfa se distribuye al pool de emisiones de esa subred. La página de Emisiones de la documentación de Bittensor describe la división del pool de emisiones, y el material 'The V440 Upgrade - The Emission Gate' describe cómo la puerta de emisión cambia la distribución. Críticamente, estos parámetros cambian cómo se distribuye la emisión, no cuándo avanza el reloj. El reloj es el tempo; la división es política.

Esta separación importa para los lectores de RPC porque significa que puedes leer el reloj (tempo, índice de época, bloques desde el último paso) sin necesidad de modelar la división de emisiones, y puedes leer el estado de emisión sin necesidad de modelar el reloj. Las dos superficies están relacionadas pero son legibles de forma independiente. Para lecturas de pesos, dividendos y emisión por UID, consulta Lectura del estado del metagrafo de Bittensor: pesos y emisión.

El pallet Subtensor almacena este estado bajo elementos de almacenamiento como SubnetEmission y Epoch. Los nombres exactos de los elementos y las claves de mapa se definen en el código fuente del pallet; trata el código Rust como autoritativo y la documentación como explicativa. Glosarios de terceros como el glosario y descripciones de hiperparámetros de la documentación de Taostats son útiles como verificación cruzada, pero no son fuentes primarias.

  • El tempo controla cuándo avanza la época.
  • Los parámetros de división de emisiones (incluida la puerta de emisión y los cambios de V440) controlan cómo se distribuye la emisión.
  • Documentado / varía por subred: los valores de tempo difieren entre subredes y pueden ser controlados por el gobernador.

La ruta de lectura de Substrate JSON-RPC para el estado de tempo y época

Subtensor expone métodos Substrate JSON-RPC. Los dos que necesitas para el reloj son state_getStorage y chain_getHeader. state_getStorage toma una clave de almacenamiento y devuelve bytes codificados en SCALE; chain_getHeader devuelve el encabezado del bloque actual, del cual se lee el número de bloque. La especificación Substrate JSON-RPC (paritytech.github.io/json-rpc y docs.polkadot.com JSON-RPC APIs) es la referencia autoritativa para la semántica de estos métodos.

Una clave de almacenamiento se construye como twox128(storage_prefix) + twox128(storage_item) + blake2_128_concat(map_key) opcional. Para un valor por subred indexado por netuid, la clave de mapa es el netuid codificado en SCALE, hasheado con blake2_128_concat. Construir esto a mano es propenso a errores; el enfoque práctico es usar los accesores tipados de @polkadot/api, que construyen la clave y decodifican el resultado por ti. Si necesitas verificar la clave sin procesar, usa state_getKeys con un prefijo para descubrir el diseño exacto de la clave en tu runtime objetivo.

La decodificación importa porque state_getStorage devuelve bytes SCALE sin procesar. Si usas accesores tipados, la decodificación está gestionada. Si llamas a state_getStorage directamente, debes decodificar los bytes devueltos según el tipo del elemento de almacenamiento definido en el código fuente del pallet. Una discrepancia entre la versión del runtime y tu decodificador es una causa común de fallos de decodificación SCALE; consulta Substrate state_getMetadata y versiones de runtime para saber cómo fijar tu decodificador al runtime.

  • state_getStorage: lee un elemento de almacenamiento por clave, devuelve bytes SCALE.
  • state_getKeys: descubre claves bajo un prefijo, útil para verificar el diseño.
  • chain_getHeader: lee la altura de bloque actual.
  • system_chain: confirma que estás conectado a la cadena esperada.

Ejemplo ejecutable en Node.js: leer tempo, índice de época y bloques desde el último paso

El ejemplo a continuación se conecta a un endpoint WSS de Subtensor, lee la cabeza de la cadena, lee tempo, índice de época y bloques desde el último paso para un netuid dado, y deriva los bloques restantes en la época más una estimación de reloj de pared usando un tiempo de bloque promedio medido sobre una ventana muestreada. Reemplaza el endpoint con tu propio endpoint WSS de Subtensor; la página de la red Bittensor Finney describe la red, y la guía RPC de Bittensor (RPC Assistant) cubre la selección de endpoints.

Los nombres de accesores usados aquí (api.query.subtensorModule.*) son los accesores tipados generados a partir de los metadatos del runtime de Subtensor. Si tu versión de runtime expone nombres de elementos diferentes, inspecciona api.query.subtensorModule en tiempo de ejecución o consulta el código fuente del pallet. El muestreo de tiempo de bloque lee chain_getHeader para los últimos N bloques y divide, por lo que la estimación de reloj de pared se mide en lugar de asumirse.

Bloques desde el último paso es un contador por subred mantenido por el runtime de Subtensor; no es derivable del número de bloque global de la cadena, porque los pasos de tempo se programan por subred y pueden estar desfasados de la fase de bloque global. Por lo tanto, el ejemplo a continuación lee el contador del almacenamiento en lugar de calcular head % tempo, e imprime los nombres de accesores disponibles de subtensorModule filtrados por /step|tempo|epoch/i para que puedas descubrir el nombre exacto del elemento en tu runtime.

// npm i @polkadot/api
import { ApiPromise, WsProvider } from '@polkadot/api';

const ENDPOINT = 'wss://your-subtensor-endpoint';
const NETUID = 1;            // subnet you are reading
const SAMPLE_BLOCKS = 100;   // block-time sampling window

async function main() {
  const api = await ApiPromise.create({ provider: new WsProvider(ENDPOINT) });
  console.log('chain:', (await api.rpc.system.chain()).toString());

  // 1. Global clock: the chain head block number.
  const header = await api.rpc.chain.getHeader();
  const head = header.number.toNumber();
  console.log('head block:', head);

  // 2. Per-netuid clock. These two accessors are the stable part of the read path.
  const tempo = await api.query.subtensorModule.tempo(NETUID);
  const epoch = await api.query.subtensorModule.epoch(NETUID);
  const tempoBlocks = tempo.toNumber();
  const epochIndex = epoch.toNumber();
  console.log('tempo (blocks):', tempoBlocks);
  console.log('epoch index:', epochIndex);

  // 3. Blocks since the last tempo step is NOT derivable as head % tempo. The counter is
  //    per-subnet and its phase is independent of the global block number. Discover the
  //    accessor on the runtime you are connected to instead of hard-coding a guess:
  const stepAccessors = Object.keys(api.query.subtensorModule).filter((k) =>
    /step|epoch|block/i.test(k)
  );
  console.log('candidate accessors on this runtime:', stepAccessors);
  // Cross-check the printed list against the pallet storage items in the Subtensor source
  // (SubnetEmission / Epoch) before selecting one.

  // 4. Measure block time from real timestamps at the window boundaries.
  const fromNumber = Math.max(1, head - SAMPLE_BLOCKS);
  const fromHash = await api.rpc.chain.getBlockHash(fromNumber);
  const fromBlockNumber = (await api.rpc.chain.getHeader(fromHash)).number.toNumber();
  const blockDelta = head - fromBlockNumber;

  const tsNow = (await api.query.timestamp.now.at(await api.rpc.chain.getBlockHash(head))).toNumber();
  const tsFrom = (await api.query.timestamp.now.at(fromHash)).toNumber();
  const elapsedSeconds = (tsNow - tsFrom) / 1000;
  const avgBlockTime = elapsedSeconds / blockDelta;
  console.log('sampled window (blocks):', blockDelta);
  console.log('avg block time (s):', avgBlockTime.toFixed(3));

  // 5. Derive the epoch ETA once you have a verified counter value.
  //    Example wiring once blocksSinceLastStep is known:
  //      const blocksRemaining = Math.max(0, tempoBlocks - blocksSinceLastStep);
  //      const etaSeconds = blocksRemaining * avgBlockTime;
  console.log('tempo is a block interval; the seconds value above is a backward-looking estimate');

  await api.disconnect();
}

main().catch((e) => { console.error(e); process.exit(1); });

Tabla de resultados: mide contra tu propio endpoint

Completa la tabla a continuación contra tu propio endpoint de Subtensor. No copies valores de este artículo; el objetivo es producir una medición reproducible que puedas volver a ejecutar. Registra el nombre de la cadena, la altura de bloque de la cabeza, el netuid, el tempo, el índice de época, los bloques desde el último paso, los bloques restantes, el tiempo de bloque promedio muestreado y los segundos estimados hasta la próxima época. Vuelve a ejecutar en dos momentos diferentes y compara el delta del índice de época para confirmar que el reloj avanza como se espera.

Si el índice de época no cambia a lo largo de una ventana mayor que tempo multiplicado por tu tiempo de bloque muestreado, trátalo como una señal para investigar: verifica que estás leyendo el netuid correcto, que tu endpoint no está sirviendo estado obsoleto o podado, y que tu decodificador coincide con la versión del runtime.

  • cadena: resultado de system_chain
  • bloque de cabeza: número de chain_getHeader
  • netuid: la subred que estás leyendo
  • tempo (bloques): subtensorModule.tempo(netuid)
  • índice de época: subtensorModule.epoch(netuid)
  • bloques desde el último paso: se lee del contador por subred en el almacenamiento de subtensorModule
  • bloques restantes en la época: tempo menos bloques desde el último paso
  • tiempo de bloque promedio muestreado (s): medido sobre tu ventana
  • segundos estimados hasta la próxima época: bloques restantes por tiempo de bloque muestreado

Método de muestreo del tiempo de bloque para estimaciones de reloj de pared

La estimación de reloj de pared es tan buena como el tiempo de bloque que le proporciones. El método es simple: lee chain_getHeader para los últimos N bloques, toma el delta del número de bloque y divide por el delta de la marca de tiempo. En cadenas Substrate, la marca de tiempo está disponible a través del pallet timestamp (api.query.timestamp.now) en un hash de bloque dado, o desde las extrínsecas inherentes del bloque. Usa una ventana lo suficientemente grande para suavizar la fluctuación pero lo suficientemente pequeña para reflejar las condiciones actuales.

Como este es un promedio retrospectivo, se retrasará respecto a los cambios de régimen en la producción de bloques. Si el tiempo de bloque cambia, tu estimación será incorrecta hasta que la ventana se renueve. Declara esta limitación explícitamente en cualquier panel que construyas. Para leer cambios de almacenamiento entre bloques en lugar de un solo punto, consulta Polkadot state_queryStorageAt: leer cambios de almacenamiento en un bloque.

Un patrón práctico es muestrear el tiempo de bloque según una programación, almacenarlo en caché y recalcular el ETA de la época a partir del valor en caché más los bloques restantes actuales. Esto evita saturar el endpoint con lecturas de encabezado en cada solicitud mientras mantiene la estimación fresca.

  • Ventana de muestreo: últimos N bloques, N elegido según tu tolerancia de latencia.
  • Lee marcas de tiempo en los límites de la ventana, no por bloque, para reducir llamadas.
  • Almacena en caché el promedio y actualízalo según una programación.
  • Documenta que la estimación es retrospectiva y se retrasará respecto a los cambios de régimen.

Solución de problemas: retornos nulos, netuid incorrecto, fallos de decodificación y estado podado

Los retornos nulos de almacenamiento son el síntoma más común. Un null de state_getStorage generalmente significa que la clave es incorrecta, que el netuid no existe o que el elemento de almacenamiento no está presente en ese bloque. Verifica el prefijo de almacenamiento y el nombre del elemento contra el código fuente del pallet, y usa state_getKeys con un prefijo para descubrir el diseño real de la clave en tu runtime objetivo. Si estás usando accesores tipados, un null a menudo significa que el netuid está fuera de rango.

Un netuid incorrecto es una causa frecuente de resultados confusos. El tempo y la época son por subred; leer netuid 0 cuando querías netuid 1 devolverá el reloj de una subred diferente. Confirma el netuid contra el metagrafo o el estado de registro de la subred antes de confiar en los valores.

Los fallos de decodificación SCALE generalmente provienen de un decodificador que no coincide con la versión del runtime. Fija tu decodificador a los metadatos del runtime y vuelve a verificar después de las actualizaciones del runtime. El almacenamiento histórico podado en nodos que no son de archivo es otra trampa: si consultas el estado en un bloque antiguo en un nodo que no lo conserva, obtendrás null o un error. Usa un nodo de archivo para lecturas históricas, o consulta en el último bloque. Finalmente, algunos endpoints rechazan por completo los métodos state_*; si state_getStorage falla con un error de método no encontrado, cambia a un endpoint que exponga la superficie completa de Substrate JSON-RPC. Para fallos relacionados con tiempos de espera, consulta Errores de tiempo de espera de RPC de Bittensor en Subtensor.

  • Retorno nulo: clave incorrecta, netuid inexistente o elemento ausente en ese bloque.
  • Netuid incorrecto: tempo y época son por subred; verifica el netuid.
  • Fallo de decodificación SCALE: el decodificador no coincide con la versión del runtime.
  • Almacenamiento histórico podado: usa un nodo de archivo para bloques antiguos.
  • Método rechazado: el endpoint no expone los métodos state_*.

Limitaciones y compensaciones de leer el reloj de la subred vía RPC

El tempo está controlado por el gobernador y puede cambiar. El tempo de una subred es un hiperparámetro, documentado / varía por subred, y no una constante global. Cualquier código que codifique un valor de tempo fijo se romperá cuando el gobernador lo cambie. Lee el tempo del almacenamiento en cada evaluación, o almacénalo en caché con un TTL corto.

Algunas subredes funcionan con tempos muy cortos, lo que significa que el índice de época puede avanzar rápidamente y tu intervalo de muestreo debe ser lo suficientemente corto para observarlo. Por el contrario, tempos largos significan que una sola muestra perdida puede parecer un reloj estancado. Los parámetros de división de emisiones, como la puerta de emisión y la actualización V440, cambian la distribución, no el reloj; no infieras el comportamiento del reloj a partir de cambios en la emisión. Finalmente, la estimación del tiempo de bloque es un promedio retrospectivo y se retrasará respecto a los cambios de régimen en la producción de bloques.

  • El tempo está controlado por el gobernador: documentado / varía por subred.
  • Tempos cortos requieren intervalos de muestreo cortos.
  • La puerta de emisión y V440 cambian la distribución, no el reloj.
  • La estimación del tiempo de bloque es retrospectiva y se retrasa respecto a los cambios de régimen.

Próximos pasos: separar las tres superficies de lectura de Bittensor

Esta página cubre la superficie de temporización tempo/época. Las otras dos superficies de lectura de Bittensor están claramente separadas: los pesos, dividendos y lecturas de emisión por UID se cubren en Lectura del estado del metagrafo de Bittensor: pesos y emisión, y la delegación, el alpha-stake y el estado del pool se cubren en Estado de staking de Bittensor vía RPC. Mantener estas superficies distintas evita el error común de mezclar lecturas del reloj con contabilidad de emisiones.

Para la selección de endpoints y el uso general de Subtensor RPC, comienza con la guía RPC de Bittensor (RPC Assistant). Para infraestructura que expone la superficie completa de Substrate JSON-RPC, consulta la página de la red Bittensor Finney, el servicio de API y los precios de RPC. Más análisis profundos de protocolos están indexados en el centro de aprendizaje de OnFinality.

  • Superficie de temporización: esta página — tempo, época, bloques desde el último paso.
  • Superficie del metagrafo: pesos, dividendos, emisión por UID.
  • Superficie de staking: delegación, alpha-stake, estado del pool.
  • Endpoint y precios: Bittensor Finney, servicio de API, precios de RPC.

Nunca te preocupes por la infraestructura nuevamente

OnFinality elimina la carga pesada de DevOps para que puedas construir de forma más inteligente y rápida.

Comenzar