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 Base: Causas, Diagnóstico y Patrones de Reintento para OP-Stack

Aprende por qué las solicitudes RPC de Base agotan el tiempo de espera, cómo diagnosticar los tiempos de espera a nivel de transporte y de método, y cómo implementar patrones de reintento robustos con retroceso exponencial para L2 de OP-Stack.

TL;DR

Los tiempos de espera de RPC en Base se deben a problemas a nivel de transporte (conexión/lectura) y a operaciones pesadas a nivel de método como eth_getLogs en rangos amplios. Este artículo explica la arquitectura de OP-Stack, proporciona un ejemplo de reintento en Node.js con retroceso exponencial y ofrece un fragmento de monitoreo para comparar la salud de los endpoints.

¿Qué causa los tiempos de espera de RPC en Base?

Un tiempo de espera de RPC en Base ocurre cuando un cliente espera demasiado tiempo una respuesta y aborta la solicitud. Esto puede suceder a nivel de transporte (tiempo de espera de conexión o lectura) o a nivel de método (el servidor tarda más que la paciencia del cliente). En Base, un L2 de OP-Stack, los tiempos de espera a menudo son provocados por métodos pesados como eth_getLogs en rangos de bloques amplios, eth_call en contratos complejos, o métodos de archivo en endpoints que no son de archivo.

Los endpoints públicos de Base agotan el tiempo de espera intermitentemente bajo carga porque sirven a muchos usuarios y pueden limitar o poner en cola las solicitudes. A diferencia de un 429 (Demasiadas solicitudes) o un código de error JSON-RPC (por ejemplo, -32005 límite excedido), un tiempo de espera es un aborto del lado del cliente: el servidor puede seguir procesando la solicitud, pero el cliente se rinde. Esta distinción es crítica para la lógica de reintento: reintentar una solicitud que agotó el tiempo de espera puede duplicar una escritura si la original era un envío de transacción, por lo que debes diseñar los reintentos cuidadosamente.

  • Tiempos de espera de transporte: tiempo de espera de conexión (handshake TCP) y tiempo de espera de lectura (esperando el cuerpo de la respuesta).
  • Tiempos de espera a nivel de método: la ejecución del lado del servidor excede el tiempo de espera configurado por el cliente.
  • Culpables comunes: eth_getLogs con rangos de bloques amplios, eth_call en contratos costosos, métodos debug_* en nodos que no son de archivo.
  • Especificidades de OP-Stack: las diferencias entre op-reth y op-node afectan el tiempo de respuesta; Flashblocks (pre-confirmaciones) pueden cambiar la cadencia de sondeo.

Arquitectura de OP-Stack y comportamiento de los tiempos de espera

Base se ejecuta en OP-Stack, con op-node (consenso) y op-reth (ejecución) como los clientes principales. op-reth está optimizado para la velocidad, pero las consultas pesadas aún toman tiempo. op-node maneja el paso de mensajes de L1 a L2 y puede introducir latencia en métodos como eth_getProof. Flashblocks, una característica que proporciona pre-confirmaciones, puede hacer que newHeads lleguen más rápido, pero también significa que tu intervalo de sondeo debe adaptarse para evitar carga innecesaria.

La documentación oficial de Base (docs.base.org) especifica los métodos RPC estándar y su comportamiento, pero no garantiza los tiempos de respuesta. Los límites específicos del proveedor (por ejemplo, el rango máximo de bloques para eth_getLogs) varían; siempre consulta la documentación de tu proveedor. Por ejemplo, algunos proveedores limitan eth_getLogs a 10,000 bloques, mientras que otros permiten más. Los tiempos de espera no están estandarizados: dependen de la configuración de tu cliente.

  • op-reth vs op-node: op-reth maneja consultas de ejecución; op-node maneja el consenso y métodos relacionados con L1.
  • Flashblocks: las pre-confirmaciones pueden reducir la percepción del tiempo de bloque, pero sondear con demasiada frecuencia puede causar tiempos de espera.
  • Métodos de archivo (eth_getBalance en bloques antiguos) requieren nodos de archivo; en endpoints que no son de archivo pueden agotar el tiempo de espera o devolver errores.

Diagnóstico de tiempos de espera: transporte vs método

Para diagnosticar un tiempo de espera, primero determina dónde ocurre. Usa curl con banderas de tiempo -w para medir el tiempo de conexión, inicio de transferencia y total. Un tiempo de espera de conexión indica problemas de red; un tiempo de espera de lectura significa que el servidor aceptó la conexión pero no respondió a tiempo. Los tiempos de espera a nivel de método son más complicados: necesitas reproducir la llamada JSON-RPC exacta y medir el tiempo de respuesta del servidor.

Aquí hay un comando curl para probar un endpoint RPC de Base con detalles de tiempo:

curl -s -o /dev/null -w "connect: %{time_connect}s\nstarttransfer: %{time_starttransfer}s\ntotal: %{time_total}s\n" \
  -X POST https://mainnet.base.org \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
# Salida esperada (ejemplo):
# connect: 0.023s
# starttransfer: 0.045s
# total: 0.046s

Implementación de reintentos con retroceso exponencial en Node.js

Un patrón de reintento robusto utiliza retroceso exponencial con jitter para evitar el efecto manada. Crucialmente, solo reintenta métodos idempotentes (eth_call, eth_getLogs, eth_blockNumber) y nunca reintentes eth_sendRawTransaction a ciegas: podrías duplicar una escritura. En su lugar, verifica el recibo de la transacción por hash después de un tiempo de espera.

A continuación se muestra un ejemplo ejecutable en Node.js usando ethers.js que implementa un tiempo de espera, reintento y retroceso para eth_getLogs, con un endpoint de respaldo. También incluye una verificación de salud usando el retraso de eth_blockNumber.

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

const endpoints = [
  'https://mainnet.base.org',
  'https://base.llamarpc.com' // ejemplo de respaldo
];

const provider = new ethers.JsonRpcProvider(endpoints[0]);
const fallbackProvider = new ethers.JsonRpcProvider(endpoints[1]);

async function callWithRetry(method, params, { timeout = 5000, retries = 3 } = {}) {
  let attempt = 0;
  while (attempt <= retries) {
    try {
      const result = await Promise.race([
        provider.send(method, params),
        new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))
      ]);
      return result;
    } catch (err) {
      if (attempt === retries) throw err;
      const delay = Math.min(1000 * 2 ** attempt, 10000) + Math.random() * 1000;
      console.log(`Intento ${attempt + 1} fallido: ${err.message}. Reintentando en ${delay}ms`);
      await new Promise(res => setTimeout(res, delay));
      attempt++;
    }
  }
}

async function getLogsWithRetry(filter) {
  // Reduce el rango de bloques para evitar tiempos de espera
  const latest = await provider.getBlockNumber();
  const fromBlock = Math.max(filter.fromBlock, latest - 1000);
  const toBlock = Math.min(filter.toBlock, latest);
  return callWithRetry('eth_getLogs', [{ ...filter, fromBlock, toBlock }]);
}

async function checkHealth() {
  const [blockNum, fallbackBlockNum] = await Promise.all([
    provider.getBlockNumber(),
    fallbackProvider.getBlockNumber()
  ]);
  const lag = Math.abs(blockNum - fallbackBlockNum);
  console.log(`Bloque primario: ${blockNum}, Bloque de respaldo: ${fallbackBlockNum}, Retraso: ${lag}`);
  return lag < 10; // saludable si el retraso < 10 bloques
}

(async () => {
  const filter = { fromBlock: 'latest', toBlock: 'latest', address: '0x...' };
  try {
    const logs = await getLogsWithRetry(filter);
    console.log(`Logs: ${logs.length}`);
  } catch (err) {
    console.error('Falló después de reintentos:', err.message);
  }
  console.log('Salud:', await checkHealth());
})();
// Salida esperada: Logs: N, Salud: true

Monitoreo de la salud del endpoint: comparando dos proveedores

Para evitar tiempos de espera, monitorea la salud de tus endpoints. Una verificación de salud simple compara eth_blockNumber y el estado de eth_syncing. Si un endpoint se retrasa significativamente o está sincronizando, cambia a otro. El siguiente fragmento ejecuta una verificación periódica y registra la latencia.

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

const endpoints = [
  { name: 'Primario', url: 'https://mainnet.base.org' },
  { name: 'Respaldo', url: 'https://base.llamarpc.com' }
];

const providers = endpoints.map(e => ({ name: e.name, provider: new ethers.JsonRpcProvider(e.url) }));

async function monitor() {
  for (const { name, provider } of providers) {
    const start = Date.now();
    try {
      const blockNumber = await provider.getBlockNumber();
      const syncing = await provider.send('eth_syncing', []);
      const latency = Date.now() - start;
      console.log(`${name}: bloque=${blockNumber}, sincronizando=${syncing}, latencia=${latency}ms`);
    } catch (err) {
      console.error(`${name} error: ${err.message}`);
    }
  }
}

setInterval(monitor, 10000); // cada 10 segundos
// Salida esperada (ejemplo):
// Primario: bloque=12345678, sincronizando=false, latencia=45ms
// Respaldo: bloque=12345678, sincronizando=false, latencia=120ms

Fallos comunes y soluciones

Aquí hay escenarios típicos de tiempo de espera y sus soluciones:

  • eth_getLogs en más de 100,000 bloques: Reduce el rango a 10,000 bloques o usa paginación. Algunos proveedores documentan un rango máximo; consulta la documentación de tu proveedor.
  • eth_call en un contrato complejo: Aumenta el tiempo de espera a 10-15 segundos, o usa eth_estimateGas para evaluar el costo.
  • debug_traceTransaction en un nodo que no es de archivo: Usa un endpoint de archivo o acepta que agotará el tiempo de espera.
  • Sondear newHeads cada 1 segundo: Usa suscripciones (eth_subscribe) en lugar de sondeo para reducir la carga y evitar tiempos de espera.
  • Límite de tasa 429: Esto no es un tiempo de espera; manéjalo retrocediendo y respetando los encabezados Retry-After.

Compensaciones y limitaciones

Los patrones de reintento añaden complejidad y pueden enmascarar problemas subyacentes. El retroceso exponencial aumenta la latencia para solicitudes legítimas. Reducir los rangos de bloques puede perder logs si no paginas correctamente. Las suscripciones requieren soporte de WebSocket, que no todos los proveedores ofrecen. Además, los límites específicos del proveedor (por ejemplo, rango máximo de bloques, límites de tasa) no están estandarizados; siempre consulta la documentación de tu proveedor.

La documentación oficial de Base (docs.base.org) describe los métodos RPC estándar pero no especifica valores de tiempo de espera. Benchmarks independientes como comparenodes proporcionan métricas de rendimiento, pero son mediciones de terceros, no garantías. Siempre verifica con tus propias pruebas.

Próximos pasos y lecturas adicionales

Ahora que entiendes los tiempos de espera de RPC en Base, aplica estos patrones a tu dApp. Para una inmersión más profunda, explora la descripción general de la red Base y el diagnóstico y soluciones genéricos de tiempos de espera de RPC. Si estás eligiendo un endpoint, consulta la configuración del endpoint RPC de Base (Asistente de RPC) y el servicio de API para opciones gestionadas. Consulta los precios de RPC para consideraciones de costos. Para más tutoriales, visita el centro de aprendizaje de OnFinality.

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