Ethereum: de historia a labs

Artículo 46 de 48

96%

completado al llegar aquí

Datos y observabilidadNota de investigación11 min de lectura

Observabilidad Ethereum para dApps

Qué medir en una dApp Ethereum: atraso del RPC, del indexador y de la finalidad, ciclo de vida de transacciones y conciliación del índice contra la cadena.

Ruta Ethereum: 0/48

Un cliente escribe a soporte: "Envié el depósito hace diez minutos y mi saldo no cambia". Abres los paneles. El servicio responde, la base de datos está sana, el proveedor de RPC no reporta errores. Todo en verde. Y sin embargo el cliente tiene razón. ¿Dónde miras?

Una dApp (aplicación descentralizada: contratos en la cadena más servicios y una interfaz fuera de ella) tiene más relojes que un backend tradicional, y cada uno puede atrasarse sin que nada "falle". Este capítulo propone qué medir para que esa pregunta tenga respuesta en minutos: atrasos entre relojes, el ciclo de vida de cada transacción y una conciliación periódica entre tu índice y la cadena.

Flujo visual

Del síntoma a la capa

Cada métrica responde una pregunta concreta sobre una capa; si una alerta no lleva a una acción, sobra.
  1. ¿La red avanza?Cabeza y finalidad de la cadena según una fuente de referencia.
  2. ¿Tu RPC la sigue?Bloques de atraso del proveedor respecto de la referencia.
  3. ¿El indexador la sigue?Distancia entre el cursor y la etiqueta que usa tu política.
  4. ¿La transacción avanzó?Pendiente, incluida, status, safe y finalized.
  5. ¿El índice dice la verdad?Conciliación contra el estado de la cadena en finalized.

Brújula de lectura

Actor, evidencia y estado

Antes de avanzar, ubica quién actúa, qué prueba queda y qué parte del sistema cambia.

Actor

Operador de la dApp

Quien recibe la alerta y tiene que decidir en qué capa está el problema.

Evidencia

Números de bloque y recibos

Cada reloj se mide en bloques o slots, y cada transacción deja un recibo con status.

Estado

Atrasos entre relojes

Lo que se vigila no es cada sistema por separado, sino la distancia entre ellos.

Preguntas guía

Estas preguntas son las que un operador debería poder responder con los paneles, sin abrir el código:

  • ¿Cuántos bloques está atrasado tu proveedor de RPC respecto de otra fuente?
  • ¿Cuánto tiempo pasa entre el bloque latest y el finalized, y es normal hoy?
  • ¿Cuántos bloques le faltan a tu indexador para alcanzar la etiqueta que usa tu política?
  • De las transacciones que enviaste en la última hora, ¿cuántas siguen pendientes y cuántas terminaron con status 0?
  • ¿Coinciden los saldos de tu índice con los que da la cadena en el bloque finalized?

Conexiones dentro de la serie

Prerequisitos internos recomendados:

Cuello de botella: cuatro relojes que pueden ir atrasados

Una dApp combina al menos cuatro relojes. El de la red: la cabeza de la cadena avanza a lo sumo un bloque por slot de 12 segundos, y algunos slots quedan vacíos. El de tu proveedor de RPC, que ve la cabeza con algún retraso. El de tu indexador, cuyo cursor va detrás del proveedor. Y el de la interfaz, que puede mostrar datos de una caché.

Cada reloj puede estar sano por separado y la experiencia rota en conjunto. El saldo del ejemplo inicial puede no aparecer porque el proveedor dejó de avanzar, porque el indexador se detuvo en una reorg, o porque tu política espera finalized y la finalidad está tardando más de lo habitual.

Por eso las métricas útiles son diferencias, no valores absolutos:

  • Atraso del RPC: número de bloque latest de una fuente de referencia menos el de tu proveedor. Si usas dos fuentes, compara las dos.
  • Atraso de finalidad: latest menos finalized. Hoy un bloque tarda unos 15 minutos en finalizar; un valor muy por encima indica un problema de la red, no tuyo.
  • Atraso del indexador: la etiqueta que usa tu política (safe o finalized) menos la altura del cursor.
  • Profundidad de reorg: cuántos bloques retrocedió el indexador en cada reorg. Es un contador, no un estado.

Las etiquetas latest, safe y finalized están definidas en la especificación de las APIs de ejecución y se piden con eth_getBlockByNumber.

Figura 1

Cuatro relojes de una dApp y las distancias que conviene medir

Fuentes:

[1][3]
Cuatro relojes de una dApp y las distancias que conviene medir
Cómo leer esta figura

Un eje horizontal de alturas de bloque que crece hacia la derecha. En él se marcan finalized, safe y latest de la red de referencia. Debajo, cuatro carriles: red, proveedor RPC, indexador e interfaz. El proveedor marca su latest un poco antes del latest de la red; la distancia se rotula atraso del RPC. El cursor del indexador está antes de safe; la distancia se rotula atraso del indexador. La distancia entre finalized y latest se rotula atraso de finalidad. La interfaz muestra un dato de caché aún más atrás.

Visual original de TrautsLab. Sobre un mismo eje de alturas de bloque, la red, el proveedor de RPC, el indexador y la interfaz marcan posiciones distintas. Las alertas miden las distancias entre esas marcas, no si cada servicio está vivo.

Traza de ejecución de una transacción vista por métricas

Las transacciones que tu dApp envía también tienen un reloj propio. Conviene registrar cada transición con marca de tiempo y el hash de la transacción como identificador común:

  1. Enviada. eth_sendRawTransaction devuelve el hash. Que el nodo la acepte no significa que vaya a entrar en un bloque.
  2. Pendiente. No hay recibo todavía. La métrica es el tiempo en este estado; si crece, revisa las comisiones ofrecidas.
  3. Incluida. eth_getTransactionReceipt devuelve un recibo con blockNumber, blockHash, gasUsed, effectiveGasPrice y status.
  4. Resultado. status vale 1 si tuvo éxito y 0 si falló. Una transacción con status 0 está incluida, cobró gas y no cambió el estado del contrato.
  5. Asentada. El bloque del recibo alcanza safe y luego finalized.

El campo status existe desde Byzantium gracias a EIP-658: antes, al introducirse el opcode REVERT, ya no se podía deducir que una transacción había fallado solo porque consumía todo el gas. Mide la tasa de status 0 por contrato y por función: un salto suele indicar un cambio en el contrato, en los parámetros o en el estado del mercado.

El costo real también se observa. Con EIP-1559, el emisor fija maxFeePerGas y maxPriorityFeePerGas, y el precio efectivo es la base del bloque más la propina, con la propina limitada para no superar el máximo: pefectivo=b+min⁡(pprioridad, pmax⁡−b)p_{\text{efectivo}} = b + \min(p_{\text{prioridad}},\ p_{\max} - b). El recibo trae ese valor en effectiveGasPrice, y eth_feeHistory devuelve la base y las propinas efectivas de un rango de bloques para comparar lo que pagaste con lo que pagaba la red.

Invariante de conciliación: el índice contra la cadena

Las métricas de atraso dicen si tu índice va al día. No dicen si es correcto. Para eso hace falta una conciliación: comparar periódicamente lo que dice tu índice con lo que dice la cadena, en un bloque que no va a cambiar.

El invariante es simple: para cada cuenta vigilada, el saldo de tu índice en el bloque finalized es igual al resultado de balanceOf(cuenta) ejecutado con eth_call en ese mismo bloque. Un desajuste indica un evento perdido, uno aplicado dos veces, un efecto de una rama huérfana o un token cuyo saldo cambia sin emitir Transfer. Cualquiera de los cuatro merece una alerta con nombre propio.

El fragmento es ilustrativo (Node 18 o superior, sin librerías). Calcula los atrasos de la sección anterior; la función emit representa tu exportador de métricas.

javascript
// Ilustrativo: atrasos entre relojes. emit() es tu exportador de métricas.
async function rpc(url, method, params) {
  const res = await fetch(url, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }),
  });
  const { result, error } = await res.json();
  if (error) throw new Error(`${method}: ${error.message}`);
  return result;
}
 
const height = async (url, tag) =>
  Number((await rpc(url, 'eth_getBlockByNumber', [tag, false])).number);
 
async function sample({ providerUrl, referenceUrl, indexerCursor }) {
  const [refLatest, provLatest, provSafe, provFinalized] = await Promise.all([
    height(referenceUrl, 'latest'),
    height(providerUrl, 'latest'),
    height(providerUrl, 'safe'),
    height(providerUrl, 'finalized'),
  ]);
  emit('rpc_head_lag_blocks', refLatest - provLatest);
  emit('finality_lag_blocks', provLatest - provFinalized);
  emit('indexer_lag_blocks', provSafe - (await indexerCursor()));
}

Riesgo operativo: alertas que llevan a una acción

Una alerta sin acción entrena al equipo a ignorarla. La regla práctica: cada alerta nombra la capa, el umbral y el primer paso del procedimiento. Los umbrales concretos dependen de tu política de confirmación y de tu tráfico; los de la tabla son puntos de partida a revisar, no valores del protocolo.

Alertas con procedimiento

Síntoma, señal y primera acción

Reglas de decisión rápida

  • El RPC va atrasado

    rpc_head_lag_blocks sostenido sobre unos pocos bloques

    → Pasar lecturas a la fuente secundaria y abrir incidencia con el proveedor.

  • La finalidad se detuvo

    finality_lag_blocks crece en todas las fuentes a la vez

    → Congelar retiros que dependen de finalized y comunicar; no cambiar la política en silencio.

  • El indexador no avanza

    indexer_lag_blocks crece mientras el RPC está al día

    → Revisar errores del bucle, reorgs profundas o límites de rango en eth_getLogs.

  • Suben los status 0

    tasa de recibos fallidos por función

    → Simular la llamada con eth_call contra el estado actual y revisar cambios del contrato.

  • La conciliación no cuadra

    saldo indexado distinto de balanceOf en finalized

    → Bloquear movimientos de esa cuenta y reprocesar desde el último bloque conciliado.

Cada fila separa un problema de la red, de un proveedor, de tu indexador o de tu contrato.

Para el nodo propio, la Beacon API ofrece un endpoint pensado para balanceadores: /eth/v1/node/health responde 200 si el nodo está listo, 206 si está sincronizando o su cliente de ejecución está en modo optimista o fuera de línea, y 503 si tiene problemas. Úsalo para sacar un nodo del pool de lecturas, no como sustituto de las métricas de atraso.

Cuándo una dApp está sana

Quédate con una frase: una dApp no está sana porque el contrato compila y los servicios responden; está sana cuando las distancias entre relojes son normales, cada transacción tiene un estado conocido y el índice cuadra con la cadena en finalized.

Referencias

Bibliografía académica

Formato IEEENumerado para ingeniería, sistemas y protocolos.
  1. [1]

    Ethereum Execution APIs specification.

    specID: ETH-EXECUTION-APISAbrir fuente (abre en otra pestaña)

    Etiquetas de bloque, recibos, effectiveGasPrice y eth_feeHistory.

  2. [2]

    Ethereum Beacon APIs specification.

    Endpoint /eth/v1/node/health y sus códigos.

  3. [3]

    ethereum.org, "Proof-of-stake (PoS)."

    official docsID: ETH-POS-DOCSAbrir fuente (abre en otra pestaña)

    Slots de 12 segundos, epochs de 32 slots y finalidad.

  4. [4]

    ethereum.org, "Single slot finality."

    official docsID: ETH-SSF-ROADMAPAbrir fuente (abre en otra pestaña)

    Tiempo actual hasta finalidad, alrededor de 15 minutos.

  5. [5]

    V. Buterin, E. Conner, R. Dudley, M. Slipper, I. Norden, and A. Bakhta, "EIP-1559: Fee market change for ETH 1.0 chain," Ethereum Improvement Proposals, 2019.

    Base fee, propina y precio efectivo.

  6. [6]

    N. Johnson, "EIP-658: Embedding transaction status code in receipts," Ethereum Improvement Proposals, 2017.

    Campo status en recibos.

Fuentes primarias, papers seminales y especificaciones técnicas citadas en esta investigación.

Rutas vecinas

Si este tema te abrió hambre, sigue por aquí.

Series y labs que comparten área o dependen de esta ruta. Pocas opciones, para no convertir la curiosidad en menú infinito.

Sigue leyendo

Ideas conectadas con este artículo

Una ruta corta para continuar sin abrir veinte pestañas y perder el hilo.

Los comentarios se cargan al final de la lectura para no meter peso antes de tiempo.