El coste de eth_getLogs está gobernado por tres dimensiones en gran medida independientes: cuántos bloques debe escanear el nodo, cuántas direcciones coinciden con el filtro y cómo está conformado el array posicional de topics. La especificación JSON-RPC de Ethereum define los topics como filtros OR por posición y un array de direcciones como un OR entre direcciones, pero la selectividad no reduce el número de bloques de los que el nodo debe ejecutar o extraer logs. Un rango de bloques estrecho con un OR amplio de topics suele ser más barato que un rango amplio con un filtro muy selectivo, porque el escaneo en sí domina. Los límites de rango por solicitud, los timeouts y los límites de resultados son específicos de cada proveedor y varían, por lo que el único modelo de coste fiable es el que mides contra tu propio endpoint. Este artículo separa la semántica documentada del protocolo del comportamiento del proveedor y ofrece una tabla de medición reproducible que rellenas tú mismo.
Las tres dimensiones de coste independientes de eth_getLogs
Una solicitud eth_getLogs tiene tres parámetros que afectan al coste cada uno mediante un mecanismo distinto: fromBlock/toBlock definen el rango de bloques, address define qué direcciones de contrato se comparan y topics define un array de filtros posicional. La especificación JSON-RPC de Ethereum los describe como criterios de filtro, no como un modelo de coste, por lo que conviene separar lo que garantiza el protocolo de lo que hace realmente un nodo de forma interna.
El rango de bloques determina cuántos bloques debe visitar el nodo. Los logs se extraen de los recibos o de los cuerpos de bloque después de la ejecución, así que el nodo generalmente no puede saltarse un bloque solo porque el filtro sea selectivo. Esta es la dimensión que más se subestima y la razón por la que un rango amplio con un filtro estrecho puede seguir siendo lento.
Las dimensiones de address y topics cambian cuántos logs se devuelven y cuánto trabajo de coincidencia se realiza por bloque, pero no cambian el número de bloques escaneados. Tratar esto como un único mando combinado de 'selectividad' es la fuente más común de intuiciones erróneas sobre el rendimiento.
- Rango de bloques: número de bloques que el nodo debe visitar y de los que debe extraer logs.
- Filtro de dirección: una sola dirección o un array, comparado como OR entre direcciones.
- Filtro de topics: array posicional donde cada posición es un OR, con null como comodín.
Semántica documentada de topics: OR por posición y comodines null
La especificación JSON-RPC de Ethereum para eth_getLogs define topics como un array de valores de 32 bytes donde el orden importa. La especificación JSON-RPC de Ethereum para eth_getLogs define topics como un array de valores de 32 bytes donde el orden importa. El primer topic es convencionalmente el hash de la firma del evento, y las posiciones siguientes corresponden a los parámetros indexados del evento. Una entrada null en una posición significa 'cualquier valor en esta posición', lo que es un comodín en lugar de un filtro.
Dentro de una misma posición, un array de valores es un OR. Así, topics: [[A, B], null, [C]] significa: coincide con logs cuyo primer topic sea A o B, cuyo segundo topic sea cualquiera y cuyo tercer topic sea C. Este es un comportamiento documentado del protocolo, no una extensión del proveedor, y es el mismo en todos los clientes conformes.
La consecuencia práctica es que un array OR anidado amplía el conjunto de coincidencias en esa posición. No reduce el escaneo de bloques. Si estás indexando un flujo de alto volumen, un OR amplio en la posición 0 puede devolver muchos más logs que una sola firma, lo que aumenta el tamaño de la respuesta y el procesamiento posterior incluso cuando el rango de bloques no cambia.
curl -s https://your-endpoint.example \
-H 'content-type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_getLogs",
"params": [{
"fromBlock": "0x11A0000",
"toBlock": "0x11A07FF",
"address": [
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"0xdAC17F958D2ee523a2206206994597C13D831ec7"
],
"topics": [
[
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"
],
null,
["0x0000000000000000000000000000000000000000000000000000000000000000"]
]
}]
}'Arrays de direcciones como OR y los límites de la selectividad
El parámetro address acepta una sola cadena de dirección o un array de direcciones. Según la especificación, un array se compara como OR entre las direcciones listadas. Esto es cómodo para indexar varios contratos, pero no reduce el escaneo de bloques: el nodo sigue visitando cada bloque del rango y comprueba cada log contra el conjunto de direcciones.
La selectividad afecta a cuántos logs se devuelven y a cuánto trabajo de coincidencia por log se realiza, pero el coste dominante para rangos amplios es el escaneo en sí. Un filtro que no coincide con ningún log a lo largo de 100.000 bloques puede seguir siendo caro porque el nodo tuvo que buscar. Esta es la razón principal por la que 'haz el filtro más selectivo' es un consejo incompleto.
Cuando necesitas indexar muchos contratos, plantéate si un único array de direcciones amplio es mejor que varias solicitudes más estrechas. Un array amplio devuelve más logs por bloque pero menos idas y vueltas; varias solicitudes estrechas pueden ser más fáciles de paralelizar y de razonar para reintentos. La elección correcta depende del comportamiento de tu endpoint, y por eso la medición importa.
- Dirección única: lo más simple, más fácil de cachear y de razonar.
- Array de direcciones: semántica OR, más logs por bloque, mismo escaneo de bloques.
- Muchas solicitudes estrechas: más idas y vueltas, paralelismo y aislamiento de reintentos más sencillos.
Por qué el rango de bloques domina el coste de escaneo
Los logs se producen durante la ejecución y se almacenan en los recibos. Para responder a eth_getLogs, un nodo debe tener acceso a los logs de cada bloque del rango solicitado, normalmente leyendo recibos o un índice. El artículo de ingeniería de Nethermind sobre consultas eth_getLogs a gran escala describe cómo los rangos amplios y las consultas repetidas estresan el almacenamiento y las rutas de filtrado, lo que concuerda con la intuición de que el rango es el principal factor de coste.
Por eso el tema complementario de los límites de rango de bloques importa tanto. Si tu proveedor limita el rango por solicitud, tendrás que trocear, y el troceado multiplica el número de solicitudes. Consulta límites de rango de bloques de eth_getLogs y troceado seguro para una estrategia de troceado que mantiene cada solicitud dentro de los límites documentados.
Un modelo mental útil: el coste es aproximadamente proporcional a los bloques escaneados, más un término menor por los logs devueltos y la coincidencia. Optimizar el filtro sin reducir el rango normalmente deja intacto el término dominante.
Matriz de decisión para flujos tipo Transfer de ERC-20 frente a eventos administrativos dispersos
Distintas formas de eventos requieren distintas estrategias de filtro. Un flujo tipo Transfer es de alto volumen y normalmente se indexa por la dirección del token y por los topics from/to. Un evento administrativo disperso es de bajo volumen y a menudo lo emite un único contrato con una firma distintiva. La tabla siguiente resume los compromisos; trátala como punto de partida, no como garantía.
Para flujos tipo Transfer, prefiere un rango de bloques acotado y una sola firma en el topic 0, con address como un único contrato o un array pequeño. Si necesitas varios tokens, un array de direcciones moderado suele ser mejor que un OR amplio de topics, porque la firma ya es específica. Para eventos administrativos dispersos, una sola dirección y una sola firma con un rango estrecho suele bastar, y puedes permitirte un rango más amplio porque el conjunto devuelto es pequeño.
La matriz trata de la forma, no de números absolutos. Los límites y timeouts de tu endpoint determinan qué es factible, y estos varían según el proveedor.
- Tipo Transfer, un token: dirección única, topic0 = firma Transfer, rango acotado.
- Tipo Transfer, muchos tokens: array de direcciones, topic0 = firma Transfer, rango troceado.
- Administrativo disperso, un contrato: dirección única, topic0 = firma administrativa, rango más amplio aceptable.
- Indexación multi-firma: array OR en topic0, pero espera más logs y respuestas más grandes.
- Dirección y topic combinados: array de direcciones más OR en topic0, la forma más amplia, mide con cuidado.
Ejemplo ejecutable en Node.js con topics anidados y un array de direcciones
El siguiente ejemplo en Node.js lanza una solicitud eth_getLogs con un array de direcciones y un array anidado de topics, y luego imprime el número de logs y el tiempo de reloj de pared. Usa el fetch global disponible en Node.js moderno, por lo que no requiere dependencias. Sustituye la URL del endpoint por la tuya.
Este ejemplo está diseñado intencionadamente para ejercitar las tres dimensiones a la vez: un rango de bloques acotado, un array de direcciones y un OR en topic0 con un comodín null y un filtro en la tercera posición. Úsalo como plantilla para tus propias mediciones.
const ENDPOINT = 'https://your-endpoint.example';
async function getLogs() {
const payload = {
jsonrpc: '2.0',
id: 1,
method: 'eth_getLogs',
params: [{
fromBlock: '0x11A0000',
toBlock: '0x11A07FF',
address: [
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
'0xdAC17F958D2ee523a2206206994597C13D831ec7'
],
topics: [
[
'0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
'0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925'
],
null,
['0x0000000000000000000000000000000000000000000000000000000000000000']
]
}]
};
const started = Date.now();
const res = await fetch(ENDPOINT, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(payload)
});
const json = await res.json();
const elapsed = Date.now() - started;
if (json.error) {
console.error('RPC error:', json.error);
return;
}
console.log('logs returned:', json.result.length);
console.log('wall ms:', elapsed);
}
getLogs().catch((err) => console.error('request failed:', err));Tabla de resultados de automedición para tu propio endpoint
Como los límites de rango por solicitud, los timeouts y los límites de resultados varían según el proveedor, el único modelo de coste fiable es el que mides. Ejecuta la misma forma de filtro contra tu endpoint con distintos rangos de bloques y registra los resultados. La tabla siguiente es una plantilla; rellénala con tus propios números.
Un experimento útil es mantener el filtro constante y variar el rango de bloques, y luego mantener el rango constante y variar la forma del filtro. Eso separa el término de escaneo del término de coincidencia. Si tu endpoint devuelve un error para un rango, registra el error en lugar de un tiempo, porque el límite en sí es el hallazgo.
No compares números entre proveedores sin anotar el endpoint, la hora del día y la forma del filtro. El comportamiento del proveedor varía según la documentación, y una sola medición no es un benchmark.
- Bloques escaneados: toBlock menos fromBlock más uno, según lo solicitado.
- Wall ms: tiempo transcurrido en el cliente alrededor de la solicitud.
- Logs devueltos: longitud del array de resultados.
- Error: registra el código y el mensaje de error JSON-RPC si se rechaza la solicitud.
| Filter shape | Blocks scanned | Wall ms | Logs returned | Error |
|--------------------------------------|----------------|---------|---------------|-------|
| single address, single topic0 | | | | |
| address array (2), single topic0 | | | | |
| single address, topic0 OR (2) | | | | |
| address array (2), topic0 OR (2) | | | | |
| single address, nested [.., null, ..]| | | | |Límites, timeouts y restricciones de resultados específicos del proveedor
La especificación JSON-RPC de Ethereum define el método y sus parámetros, pero no define un rango máximo de bloques, un timeout ni un número máximo de logs devueltos. Esas son decisiones operativas de cada operador de nodo y proveedor de RPC. En la práctica, los límites de rango por solicitud y los timeouts varían según el proveedor, y algunos proveedores también limitan el número de logs devueltos.
Esto significa que una solicitud que funciona contra un endpoint puede ser rechazada contra otro con el mismo filtro. Cuando veas un error, comprueba si es un límite de rango, un timeout o un límite de tamaño de resultado antes de cambiar tu filtro. La sección de solución de problemas a continuación cubre los casos comunes.
Si estás comparando endpoints, la página proveedores de RPC de Ethereum comparados (RPC Assistant) es un buen punto de partida, y precios de RPC explica cómo el volumen de solicitudes y el rango interactúan con el coste. Para una visión más amplia de nodos, consulta guía de nodos RPC de Ethereum.
Limitaciones y compromisos de la optimización de la forma del filtro
La optimización de la forma del filtro no puede vencer al escaneo de bloques. Si tu carga de trabajo requiere un rango amplio, lo pagarás en una sola solicitud grande o en muchas solicitudes troceadas. El troceado añade idas y vueltas y puede interactuar mal con los límites de tasa, así que el compromiso no es gratis.
Los arrays OR amplios de topics y los arrays amplios de direcciones aumentan el tamaño de la respuesta, lo que incrementa el coste de serialización y de procesamiento posterior incluso cuando el rango de bloques es pequeño. Para flujos de alto volumen, esto puede dominar. Plantéate si necesitas todas las firmas a la vez o si flujos separados son más fáciles de operar.
Por último, el comportamiento del proveedor no es una garantía del protocolo. Una estrategia que funciona hoy puede chocar mañana con un nuevo límite. Construye tu pipeline de indexación de modo que el tamaño de trozo y la forma del filtro sean configuración, no suposiciones codificadas. Para agrupar varias solicitudes, consulta buenas prácticas de batching JSON-RPC.
- El coste de escaneo no se reduce solo con la selectividad del filtro.
- El troceado cambia idas y vueltas por rangos más pequeños.
- Los filtros OR amplios aumentan el tamaño de la respuesta y el coste posterior.
- Los límites del proveedor son operativos, no definidos por el protocolo.
Solución de problemas comunes de eth_getLogs
El fallo más común es un error relacionado con el rango, a menudo devuelto como invalid params o como un mensaje específico del proveedor. Si el error menciona un rango de bloques o un límite, reduce el rango y reintenta. Si menciona un timeout, puede que el rango sea aceptable pero el filtro demasiado amplio, o que el endpoint esté lento en ese momento.
Un segundo fallo común es un resultado vacío que parece un bug. Comprueba que los valores de topic sean cadenas hex de 32 bytes, que el hash de la firma del evento coincida con el ABI y que la dirección tenga un checksum o minúsculas de forma consistente. Un null en la posición equivocada amplía silenciosamente el filtro en lugar de reducirlo.
Un tercer caso son los timeouts intermitentes bajo carga. Aquí ayuda la guía sobre timeout de RPC de Ethereum: distingue los timeouts del lado del cliente de los rechazos del servidor y añade reintentos con retroceso para fallos transitorios. Si estás obteniendo recibos de un bloque conocido, eth_getBlockReceipts frente a recibos individuales compara el enfoque masivo.
- Error de rango: reduce el intervalo fromBlock/toBlock y reintenta.
- Timeout: estrecha el filtro o el rango y reintenta con retroceso.
- Resultado vacío: verifica la longitud hex del topic, el hash de la firma y el formato de la dirección.
- Fallos intermitentes: separa el timeout del cliente del rechazo del servidor.
Próximos pasos para construir un indexador de logs consciente del coste
Empieza midiendo tu endpoint con la tabla anterior y luego elige un tamaño de trozo que se mantenga cómodamente dentro de los límites observados. Mantén la forma del filtro configurable para poder cambiar entre una dirección única y un array de direcciones sin modificar el código. Para un tratamiento más profundo de los propios parámetros de filtro, consulta Ethereum eth_getLogs: filtrar logs de eventos por dirección y topics.
Si estás evaluando endpoints para un indexador en producción, el hub de aprendizaje de OnFinality recopila guías relacionadas, y la página de servicio de API describe cómo se operan los endpoints RPC gestionados. Para detalles específicos de la red, consulta Ethereum en OnFinality.
Por último, trata cada número que veas en un artículo de blog, incluido este, como una hipótesis que debes verificar contra tu propio endpoint. La semántica del protocolo es estable; los límites operativos no lo son.