Los métodos WebSocket slotSubscribe y blockSubscribe de Solana emiten notificaciones diferentes con cadencias distintas: slotSubscribe se dispara una vez por cada slot producido con una SlotNotification que contiene slot, parent y root, mientras que blockSubscribe se dispara por bloque con una BlockNotification que contiene el bloque completo y un campo err. Los dos streams no están sincronizados: una notificación de slot normalmente precede a su notificación de bloque, root va por detrás de ambos y algunos slots no producen bloque. Los consumidores deben correlacionar por el número de slot, usar root como marca de agua de finalidad y reconciliar los huecos tras reconexiones usando getBlocks o getBlock. Este artículo explica la mecánica, proporciona un ejemplo ejecutable en Node.js y ofrece un manual de solución de problemas para construir un consumidor de notificaciones de Solana fiable.
Envoltorio de notificación y ciclo de vida de la suscripción
Las suscripciones WebSocket de Solana siguen la especificación JSON-RPC 2.0, donde una notificación es un objeto JSON con un campo jsonrpc, un campo method y un objeto params que contiene un result y un identificador subscription. El identificador de suscripción se devuelve cuando el cliente llama por primera vez al método de suscripción y debe usarse para emparejar las notificaciones entrantes con el stream correcto. Este envoltorio está definido en la especificación JSON-RPC 2.0 y es consistente en todos los métodos WebSocket de Solana.
Cuando un cliente se suscribe a slotSubscribe, el servidor responde con un ID de suscripción. A partir de ahí, el servidor envía una notificación por cada nuevo slot producido por el clúster. De forma similar, blockSubscribe devuelve un ID de suscripción y envía una notificación por cada bloque que coincide con los parámetros de suscripción. El ciclo de vida termina cuando el cliente cancela la suscripción o la conexión se cae. Como las notificaciones las inicia el servidor, el cliente debe gestionarlas de forma asíncrona y mantener estado para correlacionar slots y bloques.
El ID de suscripción es único por conexión y por método. Si un cliente abre varias suscripciones, debe enrutar las notificaciones por ID. Un error común es asumir que las notificaciones llegan en el orden en que se suscribieron; la especificación JSON-RPC 2.0 no garantiza el orden entre distintas suscripciones, y los streams de Solana son independientes.
- Envoltorio de notificación:
{ jsonrpc: '2.0', method: 'slotNotification', params: { result: {...}, subscription: <id> } } - El ID de suscripción lo devuelve la llamada inicial a
slotSubscribeoblockSubscribe. - Las notificaciones se envían de forma asíncrona; el cliente no debe bloquearse esperándolas.
Qué emite slotSubscribe: payload y cadencia de SlotNotification
El método slotSubscribe emite una SlotNotification por cada slot que produce el clúster. Según la documentación de Solana para slotSubscribe, el resultado de la notificación contiene tres campos: slot (el slot recién producido), parent (el slot padre) y root (el slot root actual). No hay datos de transacciones, ni contenido de bloque, ni indicación de si el slot contiene un bloque. Es puramente una señal de vitalidad y ordenación.
La cadencia es de aproximadamente una notificación por slot, pero el momento exacto depende de la tasa de producción de slots del clúster. Solana apunta a un tiempo de slot de alrededor de 400 milisegundos, pero esto puede variar. Como slotSubscribe no incluye datos de bloque, un consumidor que trate una notificación de slot como 'ya se puede leer un bloque' a menudo intentará obtener un bloque que aún no está disponible o que nunca se producirá (si el slot se omite).
El campo root es el slot más alto que el clúster considera enraizado, es decir, que ha sido confirmado por una supermayoría de stake. Esta es la marca de agua correcta para podar o finalizar estado de forma segura. Un consumidor no debe tratar el slot de la propia notificación como enraizado; es simplemente el último slot observado.
- Payload:
{ slot: number, parent: number, root: number } - Cadencia: una notificación por slot producido (aproximadamente cada 400 ms).
- No incluye datos de transacciones ni de bloque.
rootes la marca de agua de finalidad, no elslotde la notificación.
Qué emite blockSubscribe: BlockNotification y el campo err
El método blockSubscribe emite una BlockNotification por cada bloque que coincide con los criterios de suscripción. La documentación de Solana para blockSubscribe describe el resultado de la notificación como contenedor de slot, block y err. El campo block lleva el contenido completo del bloque, incluidas las transacciones, mientras que err es null (para un bloque exitoso) o un objeto de error si el bloque se omitió o falló. Esto permite a los consumidores evitar un viaje de ida y vuelta adicional con getBlock cuando necesitan datos de transacciones.
Como blockSubscribe solo se dispara para bloques que realmente se producen, puede omitir legítimamente slots que no produjeron bloque. Puede llegar una notificación de slot para un slot que nunca genera una notificación de bloque. Esta es una diferencia clave: slotSubscribe informa de todos los slots, mientras que blockSubscribe informa solo de bloques. El campo err es la señal de que un bloque no se produjo con éxito, pero su semántica exacta depende de los parámetros de suscripción y del comportamiento del clúster.
El método blockSubscribe requiere parámetros como filter y commitment. El filter puede ser una cadena como all o un objeto con mentionsAccountOrProgram. El nivel de commitment determina cuándo se considera disponible el bloque. Niveles de commitment más altos (por ejemplo, finalized) pueden retrasar las notificaciones pero ofrecen garantías más fuertes. El campo err de la notificación no sustituye a comprobar el estado del bloque; indica si el bloque se omitió o falló.
- Payload:
{ slot: number, block: object | null, err: object | null } - Cadencia: una notificación por bloque producido (no por slot).
- Omite los slots que no produjeron bloque.
errindica un bloque omitido o fallido;nullsignifica éxito.
Por qué los dos streams no están sincronizados
Los streams slotSubscribe y blockSubscribe son independientes y no están sincronizados. Una notificación de slot normalmente precede a su notificación de bloque porque el slot se produce antes de que el bloque se ensamble y propague por completo. Sin embargo, no se garantiza que el orden entre un slot y su notificación de bloque sea adyacente; pueden llegar otras notificaciones de slot en medio. Por lo tanto, cualquier correlación entre los dos streams debe basarse en el número de slot y no en el orden de llegada.
Root va por detrás de ambos streams. El campo root de una notificación de slot es el slot enraizado más alto, que está por detrás del slot actual. Esto significa que la notificación de bloque de un slot dado puede llegar antes de que ese slot esté enraizado. Los consumidores que requieren finalidad deben esperar a que root avance más allá del slot de interés, o usar un nivel de commitment que refleje su tolerancia al riesgo.
Como los streams no están sincronizados, un consumidor no puede asumir que recibir una notificación de slot implica que seguirá una notificación de bloque. Algunos slots se omiten por completo, y algunos bloques pueden retrasarse o no entregarse nunca debido a condiciones de red. La única forma fiable de correlacionar es rastrear el número de slot y reconciliar con getBlocks o getBlock cuando sea necesario.
- La notificación de slot normalmente precede a la de bloque, pero no de forma adyacente.
- Root va por detrás de ambos streams; un bloque puede notificarse antes de que su slot esté enraizado.
- Correlaciona por número de slot, no por orden de llegada.
- No todos los slots producen una notificación de bloque.
Usar root del stream como marca de agua de finalidad
El campo root de una SlotNotification es el slot más alto que el clúster considera enraizado. Esta es la marca de agua correcta para podar o finalizar estado de forma segura. Un consumidor debe rastrear el root máximo observado y usarlo para determinar qué slots están finalizados. Por ejemplo, si el root actual es 1000, entonces los slots hasta 1000 se consideran finales y pueden procesarse o podarse de forma segura.
Un error común es tratar el slot de la propia notificación como enraizado. El campo slot es el slot recién producido, que aún no está enraizado. Usarlo como marca de agua de finalidad llevaría a procesar datos no finalizados. En su lugar, usa siempre el campo root. Si el root se estanca (no avanza durante un periodo prolongado), puede indicar un problema de red o un problema con el proveedor RPC.
Al combinar con blockSubscribe, un consumidor puede usar el root del stream de slots para decidir cuándo un bloque está finalizado. Por ejemplo, tras recibir una notificación de bloque para el slot N, espera hasta que el root avance al menos hasta N antes de considerar el bloque final. Esto evita actuar sobre bloques que podrían revertirse.
rootes el slot enraizado más alto; úsalo como marca de agua de finalidad.- No trates el
slotde la notificación como enraizado. - El estancamiento de root puede indicar problemas de red o del proveedor.
- Combina root con las notificaciones de bloque para determinar la finalidad.
El problema de los huecos: reconexión y slots omitidos
Cuando la conexión WebSocket se cae, tanto los streams slotSubscribe como blockSubscribe pierden los slots y bloques observados durante la desconexión. Como algunos slots se omiten por completo, un consumidor no puede inferir un bloque perdido solo por un salto en el número de slot. Por ejemplo, si el último slot observado fue 100 y el siguiente es 105, los slots 101-104 pueden haberse producido pero no observado, o algunos pueden haberse omitido. Sin datos adicionales, el consumidor no puede saber cuáles.
La recuperación debe reconciliar contra getBlocks o getBlock junto con el stream. Tras reconectar, el consumidor debe consultar getBlocks para el rango entre el último slot observado y el slot actual para determinar qué slots produjeron bloques realmente. Esto se trata en detalle en el artículo sobre detección de slots omitidos y huecos de indexador con getBlocks. El mismo principio se aplica a blockSubscribe: tras una reconexión, obtén los bloques faltantes usando getBlock para los slots identificados por getBlocks.
Un consumidor robusto también debe gestionar observaciones duplicadas de slots a través de una reconexión. Si el cliente se reconecta y se vuelve a suscribir, puede recibir notificaciones de slots que ya procesó. El consumidor debe deduplicar por número de slot y mantener un estado consistente. Este es un desafío común en sistemas basados en WebSocket, y el artículo sobre reconexión de WebSocket RPC sin pérdida de datos proporciona patrones para gestionarlo.
- La reconexión pierde las notificaciones del periodo desconectado.
- Los saltos en el número de slot no implican bloques perdidos; algunos slots se omiten.
- Usa
getBlockspara reconciliar el hueco tras la reconexión. - Deduplica las observaciones de slots para evitar el doble procesamiento.
Consumidor Node.js ejecutable: seguimiento de slots, avance de root y detección de huecos
El siguiente ejemplo en Node.js se conecta a un endpoint WebSocket de Solana, se suscribe a slotSubscribe, registra el avance de root, cuenta los slots observados frente a los bloques obtenidos e informa del primer hueco tras una reconexión forzada. Usa el paquete ws para la comunicación WebSocket y la librería @solana/web3.js para el fallback HTTP. Sustituye el endpoint por la URL WebSocket de tu propio proveedor.
El ejemplo mantiene un conjunto de slots observados y una variable para el último root. En cada notificación de slot, actualiza el root y comprueba si hay huecos comparando el nuevo slot con el anterior. Si se detecta un hueco, consulta getBlocks para el rango faltante. Tras una reconexión forzada, repite la detección de huecos. Esto demuestra la mecánica central de un consumidor resiliente.
const WebSocket = require('ws');
const { Connection, clusterApiUrl } = require('@solana/web3.js');
const WS_ENDPOINT = 'wss://api.mainnet-beta.solana.com'; // Replace with your provider
const HTTP_ENDPOINT = clusterApiUrl('mainnet-beta');
const connection = new Connection(HTTP_ENDPOINT, 'confirmed');
let ws;
let subscriptionId = null;
let lastSlot = null;
let lastRoot = null;
let observedSlots = new Set();
let reconnectCount = 0;
function connect() {
ws = new WebSocket(WS_ENDPOINT);
ws.on('open', () => {
console.log('WebSocket connected');
ws.send(JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'slotSubscribe',
params: []
}));
});
ws.on('message', async (data) => {
const msg = JSON.parse(data);
if (msg.method === 'slotNotification') {
const { slot, parent, root } = msg.params.result;
const subId = msg.params.subscription;
if (subscriptionId === null) subscriptionId = subId;
console.log(`Slot: ${slot}, Parent: ${parent}, Root: ${root}`);
observedSlots.add(slot);
if (lastRoot === null || root > lastRoot) {
lastRoot = root;
console.log(`Root advanced to ${root}`);
}
if (lastSlot !== null && slot > lastSlot + 1) {
const gapStart = lastSlot + 1;
const gapEnd = slot - 1;
console.log(`Gap detected: slots ${gapStart} to ${gapEnd}`);
try {
const blocks = await connection.getBlocks(gapStart, gapEnd);
console.log(`Blocks in gap: ${blocks.length}`);
} catch (err) {
console.error('Error fetching blocks:', err);
}
}
lastSlot = slot;
}
});
ws.on('close', () => {
console.log('WebSocket closed. Reconnecting...');
reconnectCount++;
setTimeout(connect, 1000);
});
ws.on('error', (err) => {
console.error('WebSocket error:', err);
});
}
connect();
// Force a reconnect after 30 seconds for testing
setTimeout(() => {
if (ws) ws.close();
}, 30000);Tabla de resultados: stream vs payload vs cadencia vs lo que no te dice
La siguiente tabla resume las diferencias clave entre slotSubscribe y blockSubscribe. Los lectores deben verificar estas características con su propio endpoint, ya que el comportamiento específico del proveedor puede variar. Por ejemplo, algunos proveedores pueden almacenar en búfer o agrupar notificaciones, afectando a la cadencia. La tabla es una guía para construir un consumidor, no un sustituto de la medición.
Para verificar, ejecuta el ejemplo de Node.js anterior y registra las notificaciones. Compara la cadencia observada con el tiempo de slot esperado. Comprueba si llegan notificaciones de bloque para cada slot o solo para los bloques producidos. Mide el retraso entre una notificación de slot y su notificación de bloque correspondiente. Registra la tasa de avance de root. Estas mediciones te ayudarán a ajustar la lógica de timeout y reconciliación de tu consumidor.
- slotSubscribe: payload
{slot, parent, root}, cadencia por slot, no te dice si existe un bloque. - blockSubscribe: payload
{slot, block, err}, cadencia por bloque, no te informa sobre slots omitidos. - Root va por detrás de ambos streams; úsalo como marca de agua de finalidad.
- Comportamiento específico del proveedor: algunos pueden retrasar o agrupar notificaciones; verifícalo con tu propio endpoint.
Solución de problemas: notificación de bloque sin bloque esperado, estancamiento de root, slots duplicados, selección de commitment
Si recibes una notificación de bloque pero el bloque no está disponible vía getBlock, puede deberse a que el bloque se omitió o falló. Comprueba el campo err de la notificación. Si err no es nulo, el bloque no se produjo con éxito. Si err es nulo pero getBlock devuelve null, puede que el bloque aún no esté disponible en el nivel de commitment solicitado. Prueba un nivel de commitment más bajo o espera a que avance root.
El estancamiento de root —donde el campo root no avanza durante un periodo prolongado— puede indicar una partición de red o un problema con el proveedor RPC. Monitoriza la tasa de avance de root y establece una alerta si se estanca más allá de un umbral. Si root se estanca, considera cambiar a otro proveedor o endpoint. La página de red Solana de OnFinality proporciona información sobre endpoints y niveles de commitment soportados.
Las observaciones duplicadas de slots a través de una reconexión son comunes. Cuando el cliente se reconecta y se vuelve a suscribir, puede recibir notificaciones de slots que ya procesó. Deduplica manteniendo un conjunto de slots procesados e ignorando los duplicados. Si necesitas procesar cada slot exactamente una vez, usa un almacén persistente para rastrear los slots procesados entre reinicios.
La selección de commitment afecta a cuándo se entregan las notificaciones. Para blockSubscribe, el parámetro commitment determina el nivel de finalidad requerido antes de notificar un bloque. Niveles de commitment más altos (por ejemplo, finalized) ofrecen garantías más fuertes pero pueden aumentar la latencia. Elige un nivel de commitment que se ajuste a la tolerancia al riesgo de tu aplicación. El artículo sobre niveles de commitment de Solana y confirmación de transacciones explica los compromisos en detalle.
- Notificación de bloque sin bloque: comprueba el campo
erry el nivel de commitment. - Estancamiento de root: monitoriza y considera cambiar de proveedor.
- Slots duplicados: deduplica por número de slot.
- Selección de commitment: equilibra finalidad y latencia.
Limitaciones y compromisos de las suscripciones WebSocket
Las suscripciones WebSocket no sustituyen al polling HTTP en todos los casos. Ofrecen menor latencia y actualizaciones basadas en push, pero tienen estado y requieren un manejo cuidadoso de reconexiones y huecos. Si la conexión se cae, las notificaciones se pierden y el consumidor debe reconciliar. Esto añade complejidad en comparación con el polling, donde el cliente controla la cadencia de las peticiones y puede reanudar fácilmente desde el último slot procesado.
Otra limitación es que slotSubscribe no incluye datos de bloque, y blockSubscribe no incluye slots omitidos. Para obtener una imagen completa, un consumidor a menudo necesita ambos streams más fallbacks HTTP. Esto aumenta el uso de recursos y la complejidad. Además, el comportamiento específico del proveedor puede variar: algunos pueden limitar el número de suscripciones, limitar la tasa de notificaciones o tener políticas de timeout diferentes. Consulta siempre la documentación de tu proveedor.
Por último, el campo root es una marca de agua a nivel de clúster, pero no garantiza que un bloque específico esté finalizado. Un bloque puede estar enraizado pero revertirse después en casos raros. Para la mayoría de las aplicaciones, enraizado es suficiente, pero para transacciones de alto valor puede ser necesaria una confirmación adicional. La documentación de Solana proporciona los detalles autorizados sobre la semántica de las notificaciones.
- Las suscripciones WebSocket tienen estado; las reconexiones requieren reconciliación.
- Ningún stream único proporciona datos completos; combina slot, bloque y HTTP.
- Pueden aplicarse límites y limitación de tasa específicos del proveedor.
- Enraizado no garantiza finalidad absoluta en todos los casos límite.
Próximos pasos: construir un consumidor listo para producción
Para construir un consumidor listo para producción, empieza implementando el ejemplo de Node.js y midiendo la cadencia de notificaciones y el avance de root en el endpoint elegido. Usa los resultados para establecer timeouts e intervalos de reconciliación. Después, añade almacenamiento persistente para los slots procesados para gestionar reinicios y deduplicación. Integra getBlocks y getBlock para la recuperación de huecos, como se describe en el artículo de detección de huecos con getBlocks.
Considera usar un proveedor que ofrezca endpoints WebSocket fiables y documentación clara. La API WebSocket de Solana de OnFinality proporciona un punto de partida para entender los métodos disponibles. Para precios y detalles del servicio, consulta Precios de RPC y Servicio de API. El centro de aprendizaje de OnFinality contiene más artículos sobre fiabilidad y consistencia en Solana.
Por último, prueba tu consumidor en condiciones adversas: fuerza reconexiones, simula latencia de red y verifica que la recuperación de huecos funciona. Monitoriza el avance de root y alerta ante estancamientos. Con estas prácticas, puedes construir un consumidor de notificaciones de Solana resiliente que gestione la naturaleza no sincronizada de slotSubscribe y blockSubscribe.
- Mide la cadencia y el avance de root en tu endpoint.
- Implementa deduplicación persistente y recuperación de huecos.
- Elige un proveedor fiable y comprende sus límites.
- Prueba bajo reconexiones y latencia de red.