Logo
Nuevos usuarios de RPC: 35% de descuento el primer mesVer oferta
OnFinality Learn
Solución de problemas de RPC12 min de lectura

Desconexiones de RPC WebSocket de Ethereum: Causas y Reconexión con ethers.js y web3.js

Aprende por qué se caen las conexiones RPC WebSocket de Ethereum y cómo implementar una lógica de reconexión robusta en ethers.js y web3.js, con ejemplos de código ejecutables.

TL;DR

Las conexiones RPC WebSocket de Ethereum se caen debido a tiempos de espera por inactividad, límites de conexiones y problemas de red. Este artículo explica los mecanismos subyacentes y proporciona patrones de reconexión para ethers.js y web3.js, incluyendo un ejemplo ejecutable y pasos de diagnóstico.

Respuesta Directa: Por Qué Tu RPC WebSocket de Ethereum Se Desconecta Constantemente

Si estás usando endpoints RPC WebSocket de Ethereum y experimentas desconexiones frecuentes, la causa raíz suele ser una de tres cosas: tiempos de espera por inactividad impuestos por el proveedor o el balanceador de carga, límites de conexión o inestabilidad de red. La solución es implementar una estrategia de reconexión robusta que se resuscriba automáticamente a los eventos deseados. Este artículo explica los mecanismos detrás de eth_subscribe sobre WSS, por qué se caen las conexiones y cómo construir clientes resilientes con ethers.js y web3.js.

En resumen, las conexiones WebSocket no son permanentes; están sujetas a tiempos de espera y límites de recursos. Al comprender estas restricciones y programar para la reconexión, puedes mantener un flujo confiable de datos de blockchain.

Cómo Funcionan las Suscripciones WebSocket de Ethereum

Los nodos de Ethereum exponen una API JSON-RPC WebSocket que soporta el método eth_subscribe. Este método permite a los clientes suscribirse a eventos en tiempo real como nuevos encabezados de bloque, transacciones pendientes y logs. El nodo envía un ID de suscripción y luego empuja notificaciones a medida que ocurren los eventos.

La conexión WebSocket es una conexión TCP persistente con una actualización WebSocket. Tanto el cliente como el servidor pueden cerrarla. El servidor (nodo o un proxy delante de él) puede cerrar la conexión debido a inactividad, límites de recursos o mantenimiento. El cliente también puede cerrarla debido a cambios de red o lógica de la aplicación.

Por ejemplo, el servidor WebSocket de geth tiene configuraciones como --ws para habilitarlo, --ws.addr para la dirección de enlace, --ws.port y --ws.origins para restringir orígenes permitidos. El --ws.origins predeterminado es localhost, lo que puede causar desconexiones si el origen de tu cliente no está permitido. Además, geth tiene un tiempo de espera por inactividad para conexiones WebSocket, que no es configurable mediante CLI pero está establecido en 60 segundos en algunas versiones. Esto significa que si no se envían mensajes durante 60 segundos, el servidor puede cerrar la conexión. Esta es una causa común de desconexiones para suscripciones que no son muy activas, como transacciones pendientes en una red tranquila.

Para más detalles, consulta la documentación JSON-RPC de geth.

Causas Comunes de Desconexiones WebSocket

Varios factores pueden causar que tu conexión RPC WebSocket de Ethereum se caiga:

  1. Tiempos de espera por inactividad: Muchos proveedores y balanceadores de carga cierran conexiones inactivas después de un cierto período (por ejemplo, 60 segundos). Si tu suscripción no recibe eventos frecuentes, la conexión puede cerrarse.

  1. Límite de conexiones: Los nodos y proveedores limitan el número de conexiones WebSocket concurrentes por IP o por cliente. Exceder este límite puede hacer que nuevas conexiones sean rechazadas o que las existentes se caigan.

  1. Mantenimiento del proveedor: Los proveedores de infraestructura pueden reiniciar nodos o realizar mantenimiento, causando que todas las conexiones se caigan.

  1. Problemas de red: Conexiones a internet inestables, firewalls o tiempos de espera de NAT también pueden terminar conexiones WebSocket.

  1. Problemas del lado del cliente: Errores en tu código, como no manejar tramas ping/pong, pueden hacer que la conexión se considere muerta.

  1. Configuración WebSocket de geth: Como se mencionó, --ws.origins y los tiempos de espera por inactividad pueden afectar la estabilidad de la conexión.

Patrones de Reconexión en ethers.js

ethers.js proporciona una clase WebSocketProvider que envuelve una conexión WebSocket. Sin embargo, no se reconecta automáticamente. Debes implementar la lógica de reconexión tú mismo. El patrón recomendado es escuchar el evento close y luego intentar reconectarse con retroceso exponencial.

Aquí hay un ejemplo ejecutable que demuestra un bucle de reconexión simple:

const { ethers } = require('ethers');

const WS_URL = 'wss://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY'; // Reemplaza con tu endpoint

let provider;
let shouldReconnect = true;
let retryCount = 0;
const maxRetries = 10;

function createProvider() {
  provider = new ethers.WebSocketProvider(WS_URL);

  provider._websocket.on('close', (code, reason) => {
    console.log(`WebSocket cerrado: code=${code}, reason=${reason}`);
    if (shouldReconnect) {
      const delay = Math.min(1000 * 2 ** retryCount, 30000); // Retroceso exponencial
      retryCount++;
      console.log(`Reconectando en ${delay}ms...`);
      setTimeout(createProvider, delay);
    }
  });

  provider._websocket.on('error', (error) => {
    console.error('Error de WebSocket:', error);
  });

  // Resuscribirse a eventos después de la reconexión
  provider.on('block', (blockNumber) => {
    console.log('Nuevo bloque:', blockNumber);
  });
}

createProvider();

// Apagado graceful
process.on('SIGINT', () => {
  shouldReconnect = false;
  provider.destroy();
  process.exit();
});

En este ejemplo, creamos un nuevo proveedor al cerrarse y nos resuscribimos al evento 'block'. Nota que usamos provider._websocket para acceder al objeto WebSocket subyacente, que no está oficialmente documentado pero funciona en ethers.js v5. En ethers.js v6, la API puede diferir; consulta la documentación.

Otro enfoque es usar el evento on('error') del WebSocketProvider para detectar problemas de conexión y activar la reconexión. Sin embargo, el evento close es más confiable para detectar desconexiones.

Patrones de Reconexión en web3.js

web3.js también proporciona un proveedor WebSocket, pero tiene opciones de reconexión integradas. Al crear una instancia de Web3, puedes pasar un WebsocketProvider con clientConfig que incluya opciones de reconnect y delay.

Ejemplo:

const Web3 = require('web3');

const WS_URL = 'wss://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY'; // Reemplaza

const provider = new Web3.providers.WebsocketProvider(WS_URL, {
  clientConfig: {
    // Habilitar reconexión automática
    reconnect: {
      auto: true,
      delay: 5000, // ms
      maxAttempts: 10,
      onTimeout: false
    }
  }
});

const web3 = new Web3(provider);

// Suscribirse a nuevos encabezados de bloque
const subscription = web3.eth.subscribe('newBlockHeaders', (error, result) => {
  if (error) console.error(error);
  console.log('Nuevo encabezado de bloque:', result);
});

// Manejar errores del proveedor
provider.on('error', (error) => {
  console.error('Error del proveedor:', error);
});

provider.on('connect', () => {
  console.log('Conectado');
});

provider.on('end', () => {
  console.log('Conexión terminada');
});

La opción reconnect en web3.js maneja la reconexión automática, pero aún puedes necesitar resuscribirte a los eventos después de la reconexión. El evento connect se puede usar para restablecer las suscripciones.

Ten en cuenta que la reconexión automática de web3.js puede no resuscribirse automáticamente; debes manejar eso en el evento connect.

Patrón de Resuscripción Basado en Eventos

Un patrón robusto es separar la lógica de conexión de la lógica de suscripción. En cada (re)conexión, debes resuscribirte a todos los eventos deseados. Esto asegura que después de una reconexión, no te pierdas ningún dato.

Aquí hay un patrón conceptual:

let subscriptions = [];

function setupSubscriptions(provider) {
  // Limpiar suscripciones existentes
  subscriptions.forEach(sub => sub.unsubscribe());
  subscriptions = [];

  // Suscribirse a nuevos bloques
  const sub = provider.on('block', (blockNumber) => {
    console.log('Nuevo bloque:', blockNumber);
  });
  subscriptions.push(sub);

  // Suscribirse a logs (ejemplo)
  const filter = { address: '0x...' };
  const logSub = provider.on(filter, (log) => {
    console.log('Log:', log);
  });
  subscriptions.push(logSub);
}

// Llamar a setupSubscriptions después de cada conexión

Este patrón asegura que después de una reconexión, todas las suscripciones se restablezcan. También te permite gestionar las suscripciones de manera centralizada.

Diagnóstico: Obtener Bloques Recientes y Registrar el Código de Cierre

Para diagnosticar por qué tu conexión WebSocket se está cayendo, puedes escribir un script que obtenga bloques recientes y registre el código de cierre y la razón. Esto ayuda a identificar si la desconexión se debe a tiempo de espera por inactividad, apagado del servidor u otras razones.

Aquí hay un script de diagnóstico ejecutable usando ethers.js:

const { ethers } = require('ethers');

const WS_URL = 'wss://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY'; // Reemplaza

const provider = new ethers.WebSocketProvider(WS_URL);

provider._websocket.on('close', (code, reason) => {
  console.log(`Código de cierre: ${code}`);
  console.log(`Razón de cierre: ${reason}`);
  // Códigos comunes: 1000 (normal), 1006 (anormal), 1011 (error del servidor)
});

provider._websocket.on('error', (error) => {
  console.error('Error:', error);
});

// Obtener bloques recientes
async function fetchRecentBlocks() {
  const latest = await provider.getBlockNumber();
  console.log('Último bloque:', latest);
  for (let i = latest; i > latest - 5; i--) {
    const block = await provider.getBlock(i);
    console.log(`Bloque ${i}: timestamp=${block.timestamp}`);
  }
}

fetchRecentBlocks().catch(console.error);

// Mantener el proceso vivo
setInterval(() => {}, 1000);

Ejecuta este script y observa el código de cierre. Si ves el código 1006, significa que la conexión se cerró anormalmente, posiblemente debido a un problema de red o tiempo de espera del servidor. Si ves 1000, fue un cierre normal, quizás debido a tiempo de espera por inactividad.

También puedes monitorear el tiempo entre mensajes para ver si la conexión se cae después de un período de inactividad.

Fallos Comunes y Soluciones

Aquí hay problemas comunes y sus soluciones:

  1. Tiempo de espera por inactividad: Envía una trama ping periódicamente o usa una suscripción que genere eventos frecuentes. Algunos proveedores permiten establecer un intervalo de keep-alive personalizado.

  1. Límite de conexiones: Asegúrate de no abrir múltiples conexiones desde la misma IP. Usa una sola conexión y multiplexa las suscripciones.

  1. Mantenimiento del proveedor: Implementa reconexión con retroceso exponencial y jitter para evitar abrumar al servidor.

  1. Orígenes de geth: Si ejecutas tu propio nodo geth, establece --ws.origins a * o a tu origen específico para evitar rechazos de conexión.

  1. Errores del lado del cliente: Asegúrate de manejar correctamente las tramas ping y pong. La mayoría de las bibliotecas WebSocket lo hacen automáticamente, pero si usas un WebSocket crudo, debes implementarlo.

  1. Problemas de red: Usa una conexión a internet confiable y considera usar un proxy WebSocket que maneje reconexiones.

Compensaciones y Limitaciones

La lógica de reconexión añade complejidad a tu aplicación. Debes manejar la resusripción, evitar eventos duplicados y gestionar el estado. Además, la reconexión automática puede no ser adecuada para todos los casos de uso, como cuando necesitas procesar eventos en orden sin huecos.

Otra limitación es que las conexiones WebSocket no son tan confiables como HTTP para patrones de solicitud-respuesta. Si solo necesitas datos ocasionales, considera usar JSON-RPC sobre HTTP en su lugar.

También ten en cuenta que algunos proveedores pueden tener un comportamiento diferente respecto a los tiempos de espera de WebSocket. Siempre consulta la documentación del proveedor para configuraciones específicas.

Próximos Pasos y Lecturas Adicionales

Ahora que entiendes las causas y soluciones para las desconexiones WebSocket de Ethereum, puedes implementar una estrategia de reconexión robusta en tus aplicaciones. Para patrones más avanzados, considera usar bibliotecas como reconnecting-websocket o ws con reconexión integrada.

Si buscas un proveedor de RPC de Ethereum confiable, consulta la página de red de Ethereum de OnFinality y nuestro Asistente de RPC para encontrar el mejor endpoint para tus necesidades. Nuestro servicio de API ofrece soporte WebSocket robusto con manejo automático de reconexión.

Para más consejos de solución de problemas, consulta nuestra guía de soluciones generales para desconexiones WebSocket RPC. Y no olvides revisar la documentación JSON-RPC de geth para configuraciones del lado del servidor.

Explora más artículos en OnFinality Learn para profundizar tu comprensión de la infraestructura blockchain.

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