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

Tiempos de espera de RPC en Ethereum: Configuración del proveedor, eth_getLogs y patrones de reintento

Aprende por qué ocurren los tiempos de espera de RPC en Ethereum en ethers.js, viem y go-ethereum, y cómo solucionarlos con tiempos de espera del proveedor, optimización de consultas y patrones de reintento.

TL;DR

Un tiempo de espera de RPC en Ethereum ocurre cuando una solicitud de cliente a un nodo excede un límite de tiempo, a menudo debido a consultas pesadas como eth_getLogs, configuración incorrecta del proveedor o problemas de red. Este artículo explica las causas raíz, muestra cómo establecer tiempos de espera adecuados en ethers.js y viem, y proporciona patrones de reintento con retroceso.

¿Qué es un tiempo de espera de RPC en Ethereum?

Un tiempo de espera de RPC en Ethereum ocurre cuando tu aplicación envía una solicitud JSON-RPC a un nodo de Ethereum pero no recibe una respuesta dentro del tiempo que tú (o tu proveedor) han asignado. Esto es diferente de un error de límite de tasa 429 (que significa que enviaste demasiadas solicitudes) o un error JSON-RPC del nodo (que significa que el nodo procesó la solicitud pero devolvió un error). Un tiempo de espera significa que la solicitud nunca se procesó o tardó demasiado en completarse.

Las causas más comunes son los tiempos de espera del proveedor del lado del cliente (por ejemplo, el límite predeterminado de 30 segundos de ethers.js), consultas pesadas como eth_getLogs en rangos de bloques largos y cuellos de botella del lado del nodo, como el bloqueo de transacciones pendientes en go-ethereum. Comprender estas causas raíz es el primer paso para solucionarlas.

  • Tiempo de espera del lado del cliente: tu biblioteca de proveedor (ethers.js, viem) aborta la solicitud después de una duración establecida.
  • Tiempo de espera del lado del nodo: el propio nodo de Ethereum (por ejemplo, go-ethereum) tiene límites internos o bloqueos que retrasan las respuestas.
  • Tiempo de espera de red: la conexión entre tu aplicación y el endpoint RPC se cae o es demasiado lenta.

Por qué ocurren los tiempos de espera de RPC en Ethereum: Configuración del proveedor y comportamiento del nodo

En ethers.js, el tiempo de espera predeterminado para un JsonRpcProvider es de 30 segundos (la opción timeout). Si tu solicitud tarda más, el proveedor lanza un error TIMEOUT. En viem, la opción timeout predeterminada es de 10,000 ms (10 segundos) para los métodos de publicClient. Estos valores predeterminados a menudo son demasiado cortos para consultas pesadas como eth_getLogs en un rango grande o eth_call en nodos de archivo.

En el lado del nodo, go-ethereum tiene un comportamiento a nivel de método que puede causar tiempos de espera. Por ejemplo, eth_getLogs en un rango de bloques muy grande puede tardar minutos, especialmente en nodos de archivo. De manera similar, los métodos debug_ y trace_ son computacionalmente costosos y pueden bloquear la cola de ejecución del nodo. El rastreador de problemas de go-ethereum registra estos problemas: el problema #31718 discute los tiempos de espera de RPC en la versión 1.15.x, y el problema #23416 propone agregar un parámetro de tiempo de espera a las llamadas RPC. Estos están documentados como registros del rastreador de problemas, no como garantías oficiales.

Otra causa común es el bloqueo de transacciones pendientes: cuando llamas a eth_sendRawTransaction o eth_getTransactionCount con el bloque pending, el nodo puede bloquear el pool de transacciones, haciendo que otras solicitudes esperen. Esto se menciona en los problemas de geth y puede provocar tiempos de espera en cascada.

  • Tiempo de espera predeterminado de ethers.js: 30 segundos (configurable mediante la opción timeout).
  • Tiempo de espera predeterminado de viem: 10,000 ms (configurable por llamada).
  • eth_getLogs de go-ethereum puede ser lento en rangos grandes; considera paginar.
  • Los métodos debug_ y trace_ son costosos y pueden estar deshabilitados en endpoints públicos.
  • El bloqueo de transacciones pendientes puede bloquear otras llamadas RPC.

Multicall y eth_call en bucle: Tiempos de espera agregados

Las bibliotecas Multicall como Multicall3 o el agregador de 1inch te permiten agrupar múltiples solicitudes eth_call en una sola. Sin embargo, si el lote es demasiado grande o las llamadas subyacentes son pesadas (por ejemplo, leer de contratos complejos), la solicitud única puede exceder el tiempo de espera. De manera similar, hacer un bucle sobre muchas solicitudes eth_call en un bucle for puede hacer que cada una alcance el tiempo de espera individualmente, lo que lleva a una mala experiencia de usuario.

La clave es equilibrar el tamaño del lote y el tiempo de espera. Por ejemplo, un multicall con 100 verificaciones de saldo simples podría tomar 1-2 segundos, pero un multicall con 50 operaciones DeFi complejas podría tomar más de 10 segundos. Si tu tiempo de espera del proveedor es de 10 segundos, obtendrás un tiempo de espera. Necesitas aumentar el tiempo de espera o reducir el tamaño del lote.

  • Multicall3 permite agrupar múltiples eth_call en una sola solicitud.
  • Los lotes grandes pueden exceder los tiempos de espera del proveedor.
  • El eth_call en bucle puede causar múltiples tiempos de espera y límites de tasa.
  • Solución: dividir los lotes, aumentar el tiempo de espera o usar un proveedor de lotes dedicado.

Cómo establecer tiempos de espera y patrones de reintento en ethers.js y viem

En ethers.js, puedes establecer un tiempo de espera personalizado al crear un JsonRpcProvider pasando una opción timeout (en milisegundos). Por ejemplo, new ethers.JsonRpcProvider(url, network, { timeout: 60000 }) establece un tiempo de espera de 60 segundos. También puedes establecer dupTimeout para la detección de solicitudes duplicadas (predeterminado 10 segundos). Para llamadas individuales, puedes usar Promise.race con un tiempo de espera, pero el tiempo de espera a nivel de proveedor es más simple.

En viem, puedes pasar una opción timeout a métodos individuales del cliente público, como client.getLogs({ address, fromBlock, toBlock, timeout: 30_000 }). Esto anula el tiempo de espera predeterminado de 10 segundos para esa llamada.

Para los reintentos, implementa retroceso exponencial con jitter. Nunca reintentes transacciones que cambian el estado (como eth_sendRawTransaction) a ciegas, porque podrías duplicar la transacción. En su lugar, verifica el recibo de la transacción o usa un administrador de nonce. Para llamadas de solo lectura, reintentar es seguro.

  • ethers.js: new ethers.JsonRpcProvider(url, network, { timeout: 60000 }).
  • viem: client.getLogs({ ..., timeout: 30_000 }).
  • Reintenta con retroceso exponencial: espera 1s, 2s, 4s, etc., con jitter.
  • Nunca reintentes transacciones que cambian el estado sin verificar nonce/recibo.
const { ethers } = require('ethers');

async function getLogsWithRetry(provider, filter, maxRetries = 3) {
  let attempt = 0;
  while (attempt < maxRetries) {
    try {
      return await provider.getLogs(filter);
    } catch (error) {
      if (error.code === 'TIMEOUT' && attempt < maxRetries - 1) {
        const delay = Math.pow(2, attempt) * 1000 + Math.random() * 1000;
        console.log(`Timeout, retrying in ${delay}ms...`);
        await new Promise(resolve => setTimeout(resolve, delay));
        attempt++;
      } else {
        throw error;
      }
    }
  }
}

async function main() {
  const provider = new ethers.JsonRpcProvider('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY', undefined, { timeout: 30000 });
  const filter = { fromBlock: 19000000, toBlock: 19001000, address: '0x...' };
  try {
    const logs = await getLogsWithRetry(provider, filter);
    console.log(`Got ${logs.length} logs`);
  } catch (error) {
    console.error('Failed after retries:', error);
  }
}

main();
// Expected output: either "Got N logs" or "Failed after retries: ..."

Optimizando eth_getLogs y evitando tiempos de espera

La causa más común de los tiempos de espera de RPC en Ethereum es eth_getLogs en un rango de bloques grande. Por ejemplo, consultar logs desde el bloque 1 hasta 19,000,000 es imposible en la mayoría de los endpoints públicos. La solución es reducir el rango y paginar por ventanas de bloques. Por ejemplo, consulta logs en fragmentos de 10,000 bloques, y si la respuesta sigue siendo demasiado grande, reduce el tamaño del fragmento.

Otro enfoque es usar eth_subscribe para logs en tiempo real (newHeads, logs) en lugar de sondeo. Esto reduce el número de solicitudes y evita tiempos de espera para eventos en curso. Para datos históricos, considera usar un servicio de indexación dedicado o un proveedor que admita solicitudes por lotes.

El almacenamiento en caché de lecturas de almacenamiento también es efectivo. Si llamas repetidamente a eth_call para el mismo estado de contrato, almacena el resultado localmente para evitar llamadas RPC redundantes.

  • Reduce los rangos de bloques: consulta en fragmentos de 10,000 bloques o menos.
  • Usa eth_subscribe para logs en tiempo real en lugar de sondeo.
  • Almacena en caché las lecturas de almacenamiento para reducir eth_call repetidos.
  • Usa proveedores JSON-RPC por lotes donde sea compatible (por ejemplo, algunos proveedores permiten solicitudes por lotes).
// Example: paging eth_getLogs in chunks
const { ethers } = require('ethers');

async function getLogsInChunks(provider, address, fromBlock, toBlock, chunkSize = 10000) {
  let logs = [];
  for (let start = fromBlock; start <= toBlock; start += chunkSize) {
    const end = Math.min(start + chunkSize - 1, toBlock);
    const filter = { address, fromBlock: start, toBlock: end };
    try {
      const chunkLogs = await provider.getLogs(filter);
      logs = logs.concat(chunkLogs);
      console.log(`Fetched ${chunkLogs.length} logs from ${start} to ${end}`);
    } catch (error) {
      console.error(`Error fetching logs from ${start} to ${end}:`, error);
      // Optionally retry with smaller chunk
    }
  }
  return logs;
}

// Usage
// const logs = await getLogsInChunks(provider, '0x...', 19000000, 19010000);
// Expected output: "Fetched N logs from 19000000 to 19010000" etc.

Lista de verificación para solucionar tiempos de espera de RPC en Ethereum

Cuando encuentres un tiempo de espera de RPC en Ethereum, sigue esta lista de verificación para aislar la causa y aplicar la solución correcta. Este es un método sistemático que puedes reproducir en tu propio entorno.

    1. Verifica el tipo de error: ¿Es un tiempo de espera, un 429 o un error JSON-RPC? Usa el código y el mensaje de error.
    1. Revisa la configuración de tu proveedor: ¿Cuáles son los ajustes de tiempo de espera en ethers.js/viem? ¿Son demasiado bajos?
    1. Identifica el método RPC específico: ¿Es eth_getLogs, eth_call, eth_sendRawTransaction o un método debug_?
    1. Para eth_getLogs, reduce el rango de bloques y pagina por fragmentos. Prueba con un rango pequeño para confirmar.
    1. Para eth_call, verifica si la llamada al contrato es pesada (por ejemplo, bucles sobre matrices grandes). Considera almacenar en caché o usar un multicall con lotes más pequeños.
    1. Para eth_sendRawTransaction, asegúrate de no reintentar a ciegas. Usa un administrador de nonce y verifica los recibos.
    1. Prueba con un proveedor RPC diferente para descartar problemas de red.
    1. Usa eth_subscribe para datos en tiempo real en lugar de sondeo.
    1. Implementa retroceso exponencial con jitter para los reintentos, pero solo para solicitudes idempotentes.
    1. Monitorea tu volumen de solicitudes y límites de tasa para evitar 429, que también pueden causar tiempos de espera.

Compensaciones y limitaciones

Aumentar los tiempos de espera puede enmascarar problemas de rendimiento subyacentes. Una solicitud que tarda 60 segundos a menudo es una señal de una consulta ineficiente, no de una red lenta. Siempre optimiza la consulta primero, luego ajusta los tiempos de espera como último recurso.

Los patrones de reintento pueden aumentar la carga en el proveedor RPC y pueden provocar límites de tasa. Usa los reintentos con moderación y con retroceso exponencial. Para transacciones que cambian el estado, los reintentos son peligrosos; siempre verifica el estado de la transacción antes de reenviarla.

Algunos endpoints RPC públicos deshabilitan los métodos debug_ y trace_ por razones de seguridad y rendimiento. Si los necesitas, considera un nodo dedicado o un proveedor que los admita.

Las solicitudes por lotes pueden reducir el número de viajes de ida y vuelta, pero no todos los proveedores las admiten. Consulta la documentación de tu proveedor.

  • Los tiempos de espera largos pueden ocultar consultas ineficientes.
  • Los reintentos aumentan la carga y pueden causar 429.
  • Las transacciones que cambian el estado requieren un manejo cuidadoso de los reintentos.
  • No todos los proveedores admiten solicitudes por lotes o métodos costosos.

Próximos pasos y lecturas adicionales

Ahora que comprendes los tiempos de espera de RPC en Ethereum, puedes aplicar estas soluciones a tus propias aplicaciones. Para una visión más amplia de la solución de problemas de RPC, consulta nuestra guía genérica de diagnóstico y soluciones para tiempos de espera de RPC. Si estás construyendo en Ethereum, explora la mejor API RPC de Ethereum (Asistente de RPC) para comparar proveedores. Para obtener más información sobre nuestro servicio, consulta el servicio API y los precios de RPC.

Para profundizar, consulta la documentación de la API JSON-RPC de ethereum.org y la Referencia de códigos de error de Ethereum de Quicknode. Estas son fuentes autorizadas para el comportamiento de RPC y el manejo de errores.

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