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

Bittensor WebSocket RPC: Suscripciones Substrate y Flujos Confiables

Aprende a usar el RPC WebSocket de Bittensor para suscripciones Substrate en tiempo real: cabezas de cadena, almacenamiento y extrínsecos, con un ejemplo en Node.js y solución de problemas.

TL;DR

Esta guía explica el RPC WebSocket de Bittensor para suscripciones basadas en Substrate, cubriendo el formato JSON-RPC, el ciclo de vida de la conexión y un ejemplo ejecutable en Node.js usando @polkadot/api, además de solución de problemas para desconexiones comunes.

¿Qué es el RPC WebSocket de Bittensor?

El RPC WebSocket de Bittensor es la interfaz en tiempo real hacia la red Finney, una blockchain Substrate (Polkadot SDK). Permite suscribirse a actualizaciones en vivo como nuevos bloques, cabezas finalizadas, cambios de almacenamiento y estados de extrínsecos. A diferencia del RPC HTTP, WebSocket mantiene una conexión persistente, lo que permite que el nodo envíe notificaciones de forma asíncrona. Esta guía muestra cómo usarlo de manera confiable, con un enfoque en la API de suscripción JSON-RPC de Substrate.

El endpoint principal es wss://finney-rpc.onfinality.io (o la URL de tu proveedor). Los endpoints públicos a menudo tienen límites de tasa y límites de conexión, por lo que comprender el ciclo de vida es clave para construir aplicaciones robustas.

El sistema RPC de Substrate está definido por la especificación JSON-RPC 2.0, y los métodos de suscripción siguen un patrón consistente: envías una solicitud con un nombre de método que termina en _subscribe, recibes un ID de suscripción, y luego recibes notificaciones con un método _unsubscribe correspondiente para cancelar. Este patrón está documentado en la documentación de RPC de Substrate.

Para Bittensor específicamente, la red Finney ejecuta una cadena basada en Substrate con pallets personalizados para el mecanismo de incentivos. Los métodos RPC principales son estándar de Substrate, pero también puedes encontrar métodos personalizados para características específicas de Bittensor. Siempre consulta la documentación de Bittensor para obtener la lista más reciente de métodos RPC compatibles.

  • Bittensor está construido sobre Substrate, por lo que admite métodos RPC estándar de Substrate.
  • Las suscripciones WebSocket son esenciales para mineros, validadores y dApps que necesitan datos en tiempo real.
  • Esta guía cubre la lectura del estado de la cadena, no la minería/registro (que utiliza un flujo Subtensor dedicado).

API de Suscripción JSON-RPC de Substrate

La API JSON-RPC de Substrate proporciona varios métodos de suscripción. Los más comunes son: chain_subscribeNewHeads, chain_subscribeFinalizedHeads, state_subscribeStorage y author_submitAndWatchExtrinsic. Cada uno devuelve un ID de suscripción y envía notificaciones como mensajes JSON-RPC.

El formato de solicitud es estándar JSON-RPC 2.0: {"jsonrpc":"2.0","id":1,"method":"chain_subscribeNewHeads","params":[]}. La respuesta incluye un result con el ID de suscripción. Las notificaciones llegan como {"jsonrpc":"2.0","method":"chain_newHead","params":{"subscription":"sub_id","result":{...}}}.

Por ejemplo, una solicitud WebSocket cruda para suscribirse a nuevas cabezas se vería así:

{"jsonrpc":"2.0","id":1,"method":"chain_subscribeNewHeads","params":[]}
Y la respuesta podría ser:
{"jsonrpc":"2.0","result":"0x1234","id":1}
Luego recibes notificaciones como:
{"jsonrpc":"2.0","method":"chain_newHead","params":{"subscription":"0x1234","result":{"number":"0x1a2b","hash":"0x...","parentHash":"0x...","stateRoot":"0x...","extrinsicsRoot":"0x...","digest":{...}}}}

La librería @polkadot/api abstrae estos mensajes crudos, pero comprender el formato subyacente ayuda a depurar y cuando se usan otros clientes.

  • chain_subscribeNewHeads – mejores cabezas de bloque (pueden ser reorganizadas).
  • chain_subscribeFinalizedHeads – cabezas de bloque finalizadas (seguras para consenso).
  • state_subscribeStorage – cambios de almacenamiento para claves específicas.
  • author_submitAndWatchExtrinsic – rastrear el estado del extrínseco (por ejemplo, ready, inBlock, finalized).

Ciclo de Vida de la Conexión y Por Qué los Endpoints WSS Públicos se Caen

Los endpoints WSS públicos de Bittensor son recursos compartidos. Típicamente imponen tiempos de espera por inactividad (por ejemplo, 60 segundos sin mensajes), límites de conexión por IP y balanceo de carga que puede terminar las conexiones. Cuando una conexión se cae, debes reconectarte y volver a suscribirte a todas las suscripciones activas.

La librería @polkadot/api maneja la reconexión automáticamente si usas ApiPromise con la opción provider. Sin embargo, debes asegurarte de que tu código se vuelva a suscribir de forma idempotente, es decir, que pueda volver a ejecutar la lógica de suscripción de manera segura sin duplicar manejadores.

El protocolo WebSocket incluye un mecanismo de ping/pong incorporado, pero no todos los clientes lo implementan. Para mantener la conexión viva, puedes enviar una solicitud JSON-RPC como {"jsonrpc":"2.0","method":"system_health","params":[],"id":1} periódicamente. Esta es una llamada ligera que devuelve la salud del nodo y restablece el temporizador de inactividad.

Los balanceadores de carga también pueden cerrar conexiones durante mantenimiento o eventos de escalado. Implementar retroceso exponencial con jitter es una mejor práctica para evitar problemas de rebaño atronador. La documentación de Polkadot.js proporciona orientación sobre estrategias de reconexión.

  • Tiempos de espera por inactividad: envía un ping o mensaje de mantenimiento para evitar caídas.
  • Límites de conexión: limita las conexiones concurrentes por IP; usa una sola conexión para múltiples suscripciones.
  • Balanceo de carga: los nodos pueden redirigir o cerrar conexiones; implementa retroceso exponencial.

Ejemplo Ejecutable en Node.js con @polkadot/api

A continuación se muestra un script completo de Node.js que se conecta al RPC WebSocket de Bittensor, se suscribe a nuevas cabezas y cabezas finalizadas, y registra los números de bloque. También demuestra el manejo de reconexión usando los eventos on('connected') y on('disconnected').

Para ejecutarlo, instala @polkadot/api y ws (si es necesario). El script usa ApiPromise.create con el proveedor WSS. Se suscribe a subscribeNewHeads y subscribeFinalizedHeads, imprimiendo números de bloque y hashes. La salida esperada muestra un flujo de cabeceras de bloque.

El script establece autoConnect en false para controlar manualmente la conexión, lo cual es útil para implementar lógica de reconexión personalizada. Los eventos provider.on('connected') y provider.on('disconnected') te permiten registrar el estado de la conexión y activar la resuscripción si es necesario.

En un entorno de producción, envolverías la lógica de suscripción en una función que pueda llamarse nuevamente al reconectar, asegurando que no crees suscripciones duplicadas. Las funciones unsub devueltas por las llamadas de suscripción se pueden usar para limpiar antes de reconectar.

  • Usa api.rpc.chain.subscribeNewHeads() para obtener las mejores cabeceras de bloque.
  • Usa api.rpc.chain.subscribeFinalizedHeads() para cabeceras finalizadas.
  • Maneja los eventos disconnected para activar la lógica de reconexión.
// Instalar: npm install @polkadot/api
const { ApiPromise, WsProvider } = require('@polkadot/api');

const WS_URL = 'wss://finney-rpc.onfinality.io';

async function main() {
  const provider = new WsProvider(WS_URL, false); // autoConnect false para control manual
  const api = await ApiPromise.create({ provider });

  provider.on('connected', () => console.log('Conectado'));
  provider.on('disconnected', () => console.log('Desconectado'));

  // Suscribirse a nuevas cabezas (mejor bloque)
  const unsubNew = await api.rpc.chain.subscribeNewHeads((header) => {
    console.log(`Nueva cabeza: #${header.number} hash=${header.hash}`);
  });

  // Suscribirse a cabezas finalizadas
  const unsubFinal = await api.rpc.chain.subscribeFinalizedHeads((header) => {
    console.log(`Finalizada: #${header.number} hash=${header.hash}`);
  });

  // Mantener ejecución; para detener, llama a unsubNew() y unsubFinal()
}

main().catch(console.error);

// Salida esperada (ejemplo):
// Conectado
// Nueva cabeza: #123456 hash=0x...
// Finalizada: #123455 hash=0x...
// ...

Suscribirse a Cambios de Almacenamiento con Filtrado de Claves

Para monitorear elementos de almacenamiento específicos, usa state_subscribeStorage con una lista de claves de almacenamiento. Debes proporcionar la clave hash (twox_64 concat blake2_256) o usar @polkadot/api para generarla. Por ejemplo, para observar el saldo de una cuenta específica, puedes usar api.query.system.account y pasar la clave.

La notificación incluye la clave y el nuevo valor. Esto es eficiente porque solo recibes cambios para las claves que te interesan, reduciendo el ancho de banda y el procesamiento.

La generación de claves de almacenamiento sigue el esquema de hash de almacenamiento de Substrate. Para un mapa como System.Account, la clave es el hash twox_64 del nombre del pallet y del elemento concatenado con el hash blake2_256 de la dirección de la cuenta. La librería @polkadot/api maneja esto internamente cuando llamas a .key() en un objeto de consulta.

También puedes suscribirte a múltiples claves a la vez pasando un array. Esto es útil para monitorear un conjunto de cuentas o elementos de almacenamiento específicos en una sola suscripción, reduciendo el número de mensajes WebSocket.

  • Usa api.query.system.account(address).key() para obtener la clave de almacenamiento.
  • Pasa un array de claves a state_subscribeStorage.
  • El filtrado a nivel de nodo reduce la transferencia de datos.
// Ejemplo: suscribirse a cambios de saldo para una cuenta específica
const { ApiPromise, WsProvider } = require('@polkadot/api');

async function main() {
  const provider = new WsProvider('wss://finney-rpc.onfinality.io');
  const api = await ApiPromise.create({ provider });

  const address = '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY'; // ejemplo
  const key = api.query.system.account.key(address);

  const unsub = await api.rpc.state.subscribeStorage([key], (items) => {
    items.forEach(({ key, value }) => {
      console.log(`Cambio de almacenamiento: ${key} -> ${value}`);
    });
  });

  // Salida esperada: Cambio de almacenamiento: 0x... -> 0x...
}

main().catch(console.error);

Mejores vs Cabezas Finalizadas: ¿Cuál Usar?

Para mineros y validadores, la mejor cabeza (nueva cabeza) a menudo es suficiente para monitorear la producción de bloques. Sin embargo, para aplicaciones que requieren finalidad (por ejemplo, puentes entre cadenas, confirmaciones de pago), debes usar cabezas finalizadas para evitar reorganizaciones.

El consenso de Bittensor utiliza un mecanismo de peso subjetivo, pero para leer el estado de la cadena, la distinción es el comportamiento estándar de Substrate. Usa subscribeNewHeads para datos en tiempo real pero potencialmente reorganizados, y subscribeFinalizedHeads para datos irreversibles.

La cabeza finalizada está determinada por el gadget de finalidad GRANDPA, que proporciona finalidad determinista después de un cierto número de bloques. La suscripción chain_subscribeFinalizedHeads solo emite bloques que han sido finalizados por GRANDPA, lo que los hace seguros para acciones irreversibles.

En contraste, chain_subscribeNewHeads emite el último bloque que el nodo considera mejor, que puede ser reorganizado si aparece una cadena más larga. Para monitorear la producción de bloques, esto suele ser suficiente, pero para lógica de liquidación, siempre usa cabezas finalizadas.

  • Mejor cabeza: menor latencia, puede ser reorganizada.
  • Cabeza finalizada: segura para acciones irreversibles.
  • Elige según tu caso de uso: monitoreo vs. liquidación.

Fallos Comunes y Lista de Verificación para Solución de Problemas

Incluso con un cliente robusto, puedes encontrar problemas. Aquí hay fallos comunes y soluciones:

Si ves 429 Too Many Requests, estás alcanzando los límites de tasa. Reduce la frecuencia de suscripción o usa un endpoint dedicado. Para más, consulta nuestra guía de límites de tasa y 429s de Bittensor RPC.

Otro problema común es recibir 1011 Internal Error del servidor WebSocket, lo que a menudo indica que el servidor está cerrando la conexión debido a un error interno o violación de política. Verifica tu número de suscripciones y la frecuencia de mensajes.

Si ves 1008 Policy Violation, puede deberse a exceder los límites de conexión o enviar datos inválidos. Asegúrate de que tu cliente envíe solicitudes JSON-RPC adecuadas y respete los límites del servidor.

Para una lista completa de códigos de cierre de WebSocket, consulta el Registro de Códigos de Cierre de WebSocket de IANA.

  • Caídas de conexión: implementa reconexión con retroceso exponencial y vuelve a suscribirte.
  • Discrepancia de ID de suscripción: asegúrate de manejar las notificaciones con el ID de suscripción correcto.
  • Errores de clave de almacenamiento: verifica la generación de claves; usa api.query para obtener la clave correcta.
  • Tiempos de espera: envía un ping cada 30 segundos para mantener la conexión viva.
  • Para soluciones genéricas, consulta soluciones genéricas de desconexión de WebSocket RPC.

Compensaciones y Limitaciones

Los endpoints WebSocket públicos son convenientes pero tienen limitaciones: límites de tasa, límites de conexión y posible inestabilidad. Para producción, considera un endpoint dedicado o tu propio nodo. Además, ten en cuenta que la minería/registro de Bittensor utiliza un flujo Subtensor separado, no las suscripciones RPC estándar.

Los límites específicos del proveedor varían; siempre consulta la documentación de tu proveedor. Para el servicio de OnFinality, consulta servicio API y precios de RPC.

Los endpoints públicos son compartidos entre muchos usuarios, por lo que pueden experimentar mayor latencia y limitaciones ocasionales. Los endpoints dedicados proporcionan recursos garantizados y se recomiendan para aplicaciones que requieren un rendimiento consistente.

Además, las conexiones WebSocket consumen recursos del servidor, por lo que los proveedores a menudo limitan el número de conexiones concurrentes por IP. Usar una sola conexión para múltiples suscripciones es más eficiente que abrir múltiples conexiones.

  • Los endpoints públicos no son para uso pesado en producción.
  • Los endpoints dedicados ofrecen mayor confiabilidad y menor latencia.
  • Comprende la diferencia entre leer el estado de la cadena y las operaciones de minería.

Próximos Pasos y Lecturas Adicionales

Ahora que entiendes el RPC WebSocket de Bittensor, puedes construir aplicaciones en tiempo real. Para más detalles, explora la documentación oficial de Bittensor y los documentos de Polkadot.js.

Consulta nuestra guía de RPC de Bittensor (Asistente de RPC) para respuestas rápidas, y la página de la red Bittensor Finney para detalles de endpoints. Para un aprendizaje más amplio, visita el centro de aprendizaje de OnFinality.

Para profundizar en los métodos RPC de Substrate, la documentación de RPC de Substrate es una referencia autorizada. Para consideraciones específicas de WebSocket, la documentación de WebSocket de MDN proporciona una buena visión general del protocolo.

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