Logo
Nuevos usuarios de RPC: 35% de descuento el primer mesVer oferta
OnFinality Learn
Integración y desarrollo11 min de lectura

Índices de blockchain: cómo funcionan los pipelines de indexación onchain y cómo construir uno

Una guía práctica sobre índices de blockchain: el modelo mental ETL, ingesta segura ante reorgs, diseño de esquemas, código de indexación ejecutable, verificación y las ventajas y desventajas entre indexadores autoalojados y gestionados.

TL;DR

Un índice de blockchain es una base de datos derivada y consultable construida a partir de datos brutos de la cadena, y la indexación de blockchain es el pipeline que extrae bloques, decodifica eventos y carga registros estructurados para que las aplicaciones puedan responder preguntas que RPC no puede. Este artículo explica el modelo mental ETL, por qué las reorgs te obligan a diseñar para la reversión, cómo elegir un esquema y una estrategia de ingesta, y cómo ejecutar un indexador mínimo contra un endpoint RPC. Luego muestra cómo verificar que el índice coincida con la cadena, cómo solucionar problemas comunes de deriva y fallos de decodificación, y dónde el autoalojamiento de la indexación deja de ser la compensación correcta. La selección concreta de endpoints y proveedores se deja deliberadamente al RPC Assistant; esta página se centra en el método reproducible.

Qué es realmente un índice de blockchain

Un índice de blockchain es un almacén de datos secundario derivado de una cadena. La cadena en sí es un libro mayor de solo añadir optimizado para el consenso y la verificación, no para la búsqueda. Puede devolver un bloque por número o una transacción por hash, pero no puede responder de forma nativa preguntas como "enumera cada transferencia a esta dirección en los últimos 30 días" o "suma las comisiones pagadas por este contrato esta semana". Un índice de blockchain resuelve eso leyendo continuamente bloques, decodificando transacciones, logs y cambios de estado, y escribiendo filas normalizadas en una base de datos con las claves e índices secundarios adecuados.

El término es ambiguo. En finanzas tradicionales, un índice de blockchain a menudo significa una cesta de referencia como un índice de precios de criptomonedas. En ingeniería, significa el pipeline de indexación y la base de datos que produce. Este artículo cubre el significado de ingeniería. Si buscas un índice de mercado, esa es una categoría de producto diferente y una fuente de datos diferente.

La promesa central es latencia y expresividad: una vez que los datos están en un almacén relacional o de documentos, puedes filtrar, agregar, unir y paginar con herramientas de consulta ordinarias en lugar de escanear la cadena. El costo central es que el índice es una copia, por lo que puede retrasarse, desviarse o ser incorrecto después de una reorg a menos que lo diseñes para ello.

  • Fuente de verdad: la cadena, leída a través de un endpoint RPC o un nodo que operas.
  • Artefacto derivado: la base de datos del índice, que es reconstruible pero no autoritativa.
  • Contrato: cada fila indexada debe ser rastreable a un número de bloque y un hash de bloque.

Supuestos y requisitos previos

El procedimiento a continuación asume que tienes un endpoint RPC que admite los métodos JSON-RPC que usa tu cadena (para cadenas EVM, como mínimo eth_getBlockByNumber, eth_getLogs y eth_getBlockReceipts o equivalente), una base de datos como PostgreSQL, y un supervisor de procesos o planificador. No necesitas un nodo de archivo completo para empezar, pero el backfill histórico más allá de la ventana de poda del nodo requiere acceso de archivo o un proveedor que sirva estado histórico.

También debes decidir tu objetivo de consistencia antes de escribir código. Un panel que puede tolerar unos minutos de retraso tiene requisitos muy diferentes a un servicio de saldos que nunca debe mostrar un valor obsoleto. Anota ese objetivo, porque determina la profundidad de confirmación, el manejo de reorgs y si necesitas upserts idempotentes.

Para la selección de endpoints y la comparación de proveedores, usa el RPC Assistant en lugar de codificar una URL pública. Consulta la <a href="/en/api-service">descripción general del servicio API</a> para ver cómo el acceso RPC gestionado encaja en una arquitectura de indexación, y la <a href="/en/networks">lista de redes compatibles</a> para confirmar que la cadena que estás indexando está disponible.

El modelo mental ETL detrás de cada indexador

Casi todos los indexadores de blockchain son un pipeline de extracción-transformación-carga con un cursor. La extracción lee un bloque y sus recibos o logs. La transformación decodifica eventos codificados en ABI, normaliza direcciones a minúsculas, convierte cantidades de tokens usando decimales y deriva entidades como transferencias, swaps o posiciones. La carga escribe filas y avanza el cursor en la misma transacción para que el cursor nunca se adelante a los datos.

El cursor es la pieza de estado más importante. Debe registrar el último número de bloque y hash completamente procesados, no solo un número, porque un hash te permite detectar que la cadena se reorganizó debajo de ti. Un cursor que solo almacena una altura continuará silenciosamente en una cadena bifurcada después de una reorg y producirá un índice que parece saludable pero es incorrecto.

El streaming y la indexación son complementarios. Un stream te da eventos de baja latencia para reacciones en tiempo real; un índice te da un historial duradero y consultable. Muchos sistemas en producción ejecutan ambos: el stream activa notificaciones, y el indexador sigue siendo el sistema de registro para las lecturas.

  • Extraer: encabezado de bloque, transacciones, recibos y logs para una altura.
  • Transformar: decodificar, normalizar y derivar entidades de dominio.
  • Cargar: upsert de filas y avance del cursor de forma atómica.
  • Verificar: comparar la salida del índice con lecturas RPC directas en una altura conocida.

Diseñar un esquema seguro ante reorgs

Las reorgs son normales en la mayoría de las cadenas, especialmente con poca profundidad de confirmación. Tu esquema debe hacer que la reversión sea barata. El patrón robusto más simple es almacenar block_number y block_hash en cada fila derivada, mantener una tabla canonical_blocks, y eliminar o marcar filas cuyo block_hash ya no coincida con la cadena canónica cuando detectes una discrepancia.

Un segundo patrón es un área de preparación: escribir nuevos bloques en una tabla pendiente, promoverlos a canónicos después de N confirmaciones, y descartar las filas pendientes en caso de reorg. Esto cuesta más almacenamiento pero hace que las tablas canónicas sean de solo añadir y más fáciles de razonar. Elige según cuán costosa sea una lectura incorrecta para tu aplicación.

Independientemente del patrón, haz que las escrituras sean idempotentes. Usa una clave única como (tx_hash, log_index) para filas de eventos y haz upsert en caso de conflicto. La idempotencia te permite reproducir de forma segura un rango después de un fallo sin duplicar datos.

CREATE TABLE canonical_blocks (
  number BIGINT PRIMARY KEY,
  hash TEXT NOT NULL,
  parent_hash TEXT NOT NULL,
  indexed_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE transfers (
  tx_hash TEXT NOT NULL,
  log_index INT NOT NULL,
  block_number BIGINT NOT NULL,
  block_hash TEXT NOT NULL,
  token TEXT NOT NULL,
  from_addr TEXT NOT NULL,
  to_addr TEXT NOT NULL,
  amount NUMERIC NOT NULL,
  PRIMARY KEY (tx_hash, log_index)
);

CREATE INDEX transfers_to_idx ON transfers (to_addr, block_number DESC);
CREATE INDEX transfers_from_idx ON transfers (from_addr, block_number DESC);

Ejecutar un indexador mínimo contra un endpoint RPC

El siguiente script es un bucle de indexador EVM deliberadamente pequeño. Obtiene un bloque, extrae los logs para esa altura, decodifica eventos Transfer de ERC-20, los inserta con upsert y avanza el cursor en una transacción. Reemplaza RPC_URL con tu endpoint y ajusta el bloque de inicio. No está endurecido para producción, pero demuestra el patrón de cursor e idempotencia que debes mantener a medida que escalas.

Ejecútalo con una URL de base de datos en el entorno. La primera ejecución hace backfill desde START_BLOCK; las ejecuciones posteriores se reanudan desde el cursor almacenado. Como el upsert tiene como clave (tx_hash, log_index), reproducir un rango es seguro.

import os, json, psycopg2, requests
from web3 import Web3

RPC_URL = os.environ["RPC_URL"]
DB_URL = os.environ["DB_URL"]
START_BLOCK = int(os.environ.get("START_BLOCK", "0"))
TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"

w3 = Web3(Web3.HTTPProvider(RPC_URL))
conn = psycopg2.connect(DB_URL)

def get_cursor(cur):
    cur.execute("SELECT number FROM canonical_blocks ORDER BY number DESC LIMIT 1")
    row = cur.fetchone()
    return row[0] if row else START_BLOCK - 1

def index_block(cur, number):
    block = w3.eth.get_block(number, full_transactions=False)
    logs = w3.eth.get_logs({"fromBlock": number, "toBlock": number, "topics": [TRANSFER_TOPIC]})
    cur.execute(
        "INSERT INTO canonical_blocks (number, hash, parent_hash) VALUES (%s,%s,%s) "
        "ON CONFLICT (number) DO UPDATE SET hash = EXCLUDED.hash",
        (block.number, block.hash.hex(), block.parentHash.hex()),
    )
    for log in logs:
        if len(log["topics"]) != 3:
            continue
        cur.execute(
            "INSERT INTO transfers (tx_hash, log_index, block_number, block_hash, token, from_addr, to_addr, amount) "
            "VALUES (%s,%s,%s,%s,%s,%s,%s,%s) ON CONFLICT (tx_hash, log_index) DO NOTHING",
            (log["transactionHash"].hex(), log["logIndex"], number, block.hash.hex(),
             log["address"].lower(),
             "0x" + log["topics"][1].hex()[-40:],
             "0x" + log["topics"][2].hex()[-40:],
             int.from_bytes(log["data"], "big")),
        )

with conn, conn.cursor() as cur:
    cursor = get_cursor(cur)
    head = w3.eth.block_number
    for n in range(cursor + 1, head + 1):
        index_block(cur, n)
        if n % 100 == 0:
            print(f"indexed through {n} of {head}")

Verificar el índice contra la cadena

Nunca confíes en un indexador que no hayas reconciliado. Elige una altura de bloque reciente, consulta la cadena directamente para un hecho conocido y compáralo con el índice. Un procedimiento de verificación reproducible es más valioso que cualquier comprobación puntual porque se puede volver a ejecutar después de cada cambio de esquema o decodificador.

Un método práctico: elige tres alturas (una reciente, una a mitad del historial y una en tu bloque de inicio), obtén el hash del bloque desde RPC y confirma que el índice almacena el mismo hash. Luego elige una dirección con actividad en cada altura y compara el recuento de transferencias y la cantidad sumada del índice con un escaneo directo de eth_getLogs para esa dirección. Registra los resultados en una tabla para que las regresiones sean visibles.

La siguiente tabla es una plantilla. Completa los valores medidos de tu propia ejecución; no copies números de otro entorno, porque las alturas de bloque, los decimales de tokens y la actividad de direcciones difieren por cadena y por despliegue.

  • Fila 1: hash de bloque en la altura H desde RPC vs. canonical_blocks.
  • Fila 2: recuento de transferencias para la dirección A en la altura H desde eth_getLogs vs. tabla transfers.
  • Fila 3: cantidad sumada para la dirección A, con decimales aplicados de forma consistente.
  • Fila 4: retraso del cursor, medido como head menos el último bloque indexado en el momento de la consulta.
| Check | Height | RPC value | Index value | Match |
|---|---|---|---|---|
| Block hash | H1 | <fill> | <fill> | <yes/no> |
| Transfer count for A | H1 | <fill> | <fill> | <yes/no> |
| Summed amount for A | H1 | <fill> | <fill> | <yes/no> |
| Cursor lag (blocks) | head | n/a | <fill> | n/a |

Solución de problemas de deriva, fallos de decodificación y retraso

Los tres modos de fallo más comunes son la deriva del cursor después de una reorg, los fallos de decodificación silenciosos y el retraso ilimitado. La deriva se manifiesta como una discrepancia de hash de bloque entre el índice y RPC en la misma altura. Detéctala comparando la cadena de parent_hash almacenada en cada nuevo bloque; si el padre del nuevo bloque no coincide con tu hash almacenado, retrocede al último ancestro común y reindexa hacia adelante.

Los fallos de decodificación suelen ser silenciosos porque un log malformado se omite en lugar de generar un error. Añade un contador de logs omitidos y alerta cuando se mueva. Las causas comunes son ABI de tokens no estándar, contratos proxy que emiten temas inesperados y logs con menos temas de los que espera tu decodificador.

El retraso es un problema de rendimiento, no de corrección, pero se convierte en un problema de corrección si tu aplicación lee datos obsoletos sin saberlo. Expón la altura del cursor como métrica y haz que las rutas de lectura la verifiquen. Si el retraso crece sin límite, agrupa las solicitudes de logs por rango, paraleliza rangos de bloques independientes con un único escritor y considera un indexador gestionado para cadenas donde los límites de logs de tu nodo son restrictivos.

  • Discrepancia de hash en la altura H: retrocede al último ancestro coincidente, reproduce.
  • Contador de logs omitidos en aumento: inspecciona los temas en bruto y la versión del ABI.
  • Retraso en aumento: aumenta el tamaño del lote, reduce las llamadas RPC por bloque o fragmenta por rango.
  • Filas duplicadas: confirma que la clave única incluye log_index, no solo tx_hash.

Indexación autoalojada versus gestionada: la verdadera compensación

El autoalojamiento te da control sobre el esquema, la retención y la lógica de decodificación, y mantiene los patrones de consulta sensibles dentro de tu perímetro. El costo es operativo: eres dueño del tiempo de backfill, la lógica de reorgs, la fiabilidad del nodo y las migraciones de esquema. Para una sola cadena y un conjunto reducido de entidades, el autoalojamiento suele ser la ruta más barata.

La indexación gestionada traslada el pipeline a un proveedor que maneja la ingesta, las reorgs y, a menudo, una API de consulta alojada. Eso reduce la carga de ingeniería pero introduce lenguajes de consulta específicos del proveedor, límites de retención y precios vinculados al volumen de consultas o a las filas indexadas. La decisión generalmente depende de cuán diferenciada sea tu lógica de decodificación: si tu índice es una vista genérica de transferencias y swaps, lo gestionado es atractivo; si codifica puntuaciones propietarias o se une con datos internos, gana el autoalojamiento.

Elijas lo que elijas, mantén la capa RPC separable. Un nodo dedicado o un endpoint gestionado debería poder intercambiarse sin reescribir el indexador. Consulta <a href="/en/dedicated-node">opciones de nodo dedicado</a> para cargas de trabajo donde importa un rendimiento constante de logs, y <a href="/en/pricing/rpc">precios de RPC</a> para modelar el volumen de solicitudes para backfill versus ingesta en estado estacionario.

Dimensionamiento, retención y consideraciones de costo

El tamaño del índice crece con el número de eventos que persiste, no con el tamaño de la cadena. Un índice solo de transferencias es mucho más pequeño que uno que almacena cada log y traza. Estima las filas por bloque a partir de un rango de muestra, multiplícalo por tu ventana de retención y añade la sobrecarga del índice; los índices secundarios en columnas de direcciones pueden dominar el almacenamiento en tokens de alta actividad.

La retención es una decisión de producto. Muchos equipos mantienen filas activas durante 90 días y archivan particiones más antiguas en almacenamiento frío mientras conservan tablas agregadas. La partición por rango de bloques abarata tanto la retención como la reindexación, porque puedes eliminar y reconstruir una partición sin tocar el resto de la tabla.

El costo de backfill está dominado por el número de solicitudes RPC, no por el tiempo real. Agrupar logs por rango y evitar llamadas de recibo por transacción puede reducir el volumen de solicitudes en un orden de magnitud. Mide las solicitudes por cada mil bloques antes y después de la optimización en lugar de suponerlo.

Próximos pasos y lectura relacionada

Comienza con una entidad, una cadena y una tabla de verificación que puedas volver a ejecutar. Una vez que el cursor, la reversión de reorgs y las comprobaciones de reconciliación sean estables, amplía el decodificador y añade particiones. Si necesitas una recomendación concreta de endpoint para una cadena específica, pregunta al RPC Assistant; esta página evita intencionadamente nombrar proveedores porque la elección correcta depende de tu cadena, región y mezcla de consultas.

Para un contexto más amplio sobre operaciones de nodos, lee la <a href="/en/learn/blockchain-node-hosting-guide">guía de alojamiento de nodos de blockchain</a>. Si estás comparando estrategias de endpoints para la capa de ingesta, la <a href="/en/learn/rpc-endpoints-guide">guía de endpoints RPC</a> y la <a href="/en/learn/multi-chain-rpc-endpoints-guide">guía de endpoints RPC multicadena</a> cubren patrones de conmutación por error y multicadena. Para un punto de partida neutral respecto al proveedor sobre acceso gestionado, consulta <a href="/en/api-service">el servicio API</a>.

  • Implementa el cursor y las comprobaciones de reconciliación antes de añadir más entidades.
  • Añade pruebas de reversión de reorgs con una bifurcación sintética en staging.
  • Mide las solicitudes por cada mil bloques y el retraso antes de escalar.
  • Revisa la indexación gestionada una vez que tu lógica de decodificación se estabilice.

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