eth_getLogs te permite recuperar registros de eventos de Ethereum filtrando por la dirección del contrato emisor y hasta cuatro posiciones de temas. El primer tema es siempre el hash de la firma del evento; los siguientes tres corresponden a parámetros indexados, que se almacenan como hashes de 32 bytes. Para consultar de manera eficiente, debes comprender cómo se codifican los parámetros indexados, cómo combinar filtros con lógica OR y cómo paginar a través de grandes rangos de bloques porque los proveedores imponen sus propios límites. Esta guía explica el mecanismo, proporciona ejemplos ejecutables y ofrece una lista de verificación para solucionar problemas.
Respuesta directa: Cómo filtrar registros de eventos correctamente
Para filtrar registros de eventos de Ethereum con eth_getLogs, envías una solicitud JSON-RPC con una address (o un array de direcciones) y un array de topics. El array de topics puede tener hasta cuatro entradas, cada una correspondiente a la posición de tema en el registro. El primer tema es el hash de la firma del evento (por ejemplo, keccak256("Transfer(address,address,uint256)")), y los siguientes tres corresponden a parámetros indexados. Usa null para coincidir con cualquier valor en una posición, o un array para aplicar OR a múltiples valores dentro de esa posición. Por ejemplo, para obtener todos los eventos de transferencia ERC-20 de un token específico donde el remitente es una dirección particular, estableces el primer tema al hash de la firma de Transfer y el segundo tema a la dirección del remitente rellenada a la izquierda con 32 bytes. Esta guía explica los mecanismos, proporciona ejemplos ejecutables y explica cómo manejar los límites del proveedor y la paginación.
Si eres nuevo en RPC de Ethereum, consulta la descripción general de la red Ethereum y el centro de aprendizaje de OnFinality para obtener contexto fundamental.
Cómo funcionan los registros de eventos y los temas internamente
Cuando un contrato inteligente emite un evento, la Máquina Virtual de Ethereum (EVM) registra una entrada de registro en el recibo de la transacción. Cada registro tiene una dirección del emisor (el contrato que lo emitió), un array de topics y un campo data. El array de topics contiene hasta cuatro valores de 32 bytes: el primero es siempre el hash de la firma del evento, calculado como keccak256("EventName(type1,type2,...)") donde los tipos son los tipos canónicos de Solidity (por ejemplo, address, uint256, bool). Los tres temas restantes corresponden a parámetros indexados, en el orden en que aparecen en la declaración del evento. Los parámetros indexados se almacenan como valores de 32 bytes: para tipos de valor como address y uint256, el valor se rellena a la izquierda con ceros hasta 32 bytes; para tipos de referencia como string, bytes y arrays, el valor es el hash keccak256 de los datos reales. Los parámetros no indexados se codifican ABI y se concatenan en el campo data.
Esta codificación se especifica en la documentación de Solidity sobre eventos y en la especificación de la API de ejecución de Ethereum para eth_getLogs. Comprender esto es crucial porque no puedes filtrar directamente por parámetros no indexados; debes decodificar el campo data después de la recuperación.
El método eth_getLogs acepta un parámetro address que puede ser una dirección única o un array de direcciones (lógica OR). El parámetro topics es un array de hasta cuatro entradas de filtro. Cada entrada puede ser un valor único de 32 bytes, un array de valores de 32 bytes (OR dentro de esa posición), o null para coincidir con cualquier valor. El rango de bloques se especifica con fromBlock y toBlock, que pueden ser un número de bloque hexadecimal o una etiqueta como latest, earliest o pending. Si se omite, el rango por defecto es latest para ambos, lo que significa que solo se devuelven registros del bloque más reciente; esto es un error común.
Para una inmersión más profunda sobre cómo se almacenan los registros y por qué el escaneo es lento, consulta la discusión de Ethereum Stack Exchange como referencia independiente.
- Los registros se almacenan en el recibo de la transacción, no en el almacenamiento del contrato.
- El primer tema es siempre el hash de la firma del evento.
- Los parámetros indexados se limitan a tres por evento.
- Los parámetros no indexados están en el campo
datay no se pueden filtrar. - Las direcciones en los temas se rellenan a la izquierda hasta 32 bytes (por ejemplo,
0x0000...0000abc...).
Construyendo un filtro correcto para eventos de transferencia ERC-20
Construyamos un filtro para eventos de transferencia ERC-20. La firma del evento es Transfer(address indexed from, address indexed to, uint256 value). El primer tema es keccak256("Transfer(address,address,uint256)"), que es 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. El segundo tema es la dirección from (rellenada a la izquierda), y el tercero es la dirección to. El value no está indexado, por lo que aparece en el campo data.
Para obtener todas las transferencias de un remitente específico para un token específico, establecerías la address al contrato del token, el primer tema al hash de la firma y el segundo tema a la dirección del remitente rellenada a 32 bytes. Por ejemplo, para obtener transferencias de 0x1234... en el contrato USDC, el segundo tema sería 0x0000000000000000000000001234... (con la dirección alineada a la derecha).
Si deseas filtrar tanto por from como por to, puedes proporcionar un array para el segundo tema para aplicar OR a múltiples remitentes, o usar null para coincidir con cualquiera. Por ejemplo, para obtener transferencias desde o hacia una dirección específica, necesitarías dos llamadas separadas porque from y to están en diferentes posiciones de tema y no se pueden combinar con OR entre posiciones.
Aquí tienes un ejemplo concreto usando curl para consultar la red principal de Ethereum (reemplaza la URL RPC con el endpoint de tu proveedor, por ejemplo, del servicio de API):
curl -X POST https://your-rpc-endpoint -H "Content-Type: application/json" --data '{
"jsonrpc": "2.0",
"method": "eth_getLogs",
"params": [{
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000001234567890abcdef1234567890abcdef12345678"
],
"fromBlock": "0x1000000",
"toBlock": "0x1000100"
}],
"id": 1
}'Comprendiendo la forma de la respuesta y el manejo de reorganizaciones
La respuesta de eth_getLogs es un array de objetos de registro. Cada objeto de registro contiene los siguientes campos: address (el contrato que emitió el registro), topics (array de temas de 32 bytes), data (datos no indexados codificados en hexadecimal), blockNumber (hexadecimal), transactionHash, transactionIndex, blockHash, logIndex y removed (un booleano que indica si el registro fue eliminado debido a una reorganización de la cadena). El indicador removed es importante para aplicaciones que rastrean registros en tiempo real: si un registro aparece con removed: true, significa que el bloque fue reorganizado y el registro ya no es válido.
Los registros se devuelven en orden de recibo, pero no hay garantía de orden global entre múltiples bloques. Si necesitas procesar registros secuencialmente, debes ordenarlos por blockNumber y logIndex.
Aquí tienes un ejemplo de respuesta para un solo registro:
{
"jsonrpc": "2.0",
"id": 1,
"result": [
{
"address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000001234567890abcdef1234567890abcdef12345678",
"0x000000000000000000000000abcdefabcdefabcdefabcdefabcdefabcdefabcd"
],
"data": "0x0000000000000000000000000000000000000000000000000000000000000064",
"blockNumber": "0x1000100",
"transactionHash": "0x...",
"transactionIndex": "0x0",
"blockHash": "0x...",
"logIndex": "0x0",
"removed": false
}
]
}Rendimiento: Por qué las consultas de rango amplio son lentas y cómo paginar
Escanear un rango de bloques amplio con eth_getLogs es lento porque el nodo debe iterar sobre cada bloque en el rango, cargar el filtro de floración del bloque y verificar si la dirección/temas solicitados podrían coincidir. Esta es una operación intensiva en CPU, especialmente en nodos de archivo. Los proveedores a menudo imponen límites en el número de registros devueltos o en el lapso de bloques por solicitud para proteger su infraestructura. Estos límites están documentados / varían según el proveedor; debes consultar la documentación de tu proveedor (por ejemplo, la documentación de eth_getLogs de Alchemy o la documentación de Infura como referencias independientes).
Para manejar consultas grandes, debes paginar dividiendo el rango de bloques en fragmentos. Una estrategia común es consultar en fragmentos de tamaño fijo (por ejemplo, 10,000 bloques) y luego, si la respuesta está completa, continuar desde el último número de bloque que recibiste. Sin embargo, debido a que los registros no están garantizados en orden, un enfoque más seguro es usar el toBlock del fragmento actual como el fromBlock del siguiente fragmento, pero restar uno para evitar duplicados. Alternativamente, puedes usar el blockNumber del último registro devuelto como el siguiente fromBlock, pero esto puede omitir registros si la respuesta se trunca a mitad de bloque.
El siguiente script de Node.js demuestra un enfoque de consulta fragmentada. Utiliza la API fetch y está diseñado para que el lector lo ejecute y mida los límites de su propio proveedor. Completa la tabla de resultados a continuación con tus observaciones.
- Tamaño del fragmento: comienza con 10,000 bloques y ajusta según los límites del proveedor.
- Si recibes un error que indica demasiados resultados, reduce el tamaño del fragmento.
- Maneja siempre el indicador
removedsi procesas registros en tiempo real. - Para monitoreo continuo, considera usar
eth_newFilteryeth_getFilterChangesen lugar de llamadas repetidas aeth_getLogs.
const rpcUrl = 'https://your-rpc-endpoint';
async function getLogs(fromBlock, toBlock) {
const response = await fetch(rpcUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
method: 'eth_getLogs',
params: [{
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
topics: ['0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'],
fromBlock: '0x' + fromBlock.toString(16),
toBlock: '0x' + toBlock.toString(16)
}],
id: 1
})
});
const data = await response.json();
if (data.error) throw new Error(data.error.message);
return data.result;
}
async function fetchAllLogs(startBlock, endBlock, chunkSize) {
let allLogs = [];
let currentStart = startBlock;
while (currentStart <= endBlock) {
const currentEnd = Math.min(currentStart + chunkSize - 1, endBlock);
const logs = await getLogs(currentStart, currentEnd);
allLogs = allLogs.concat(logs);
console.log(`Fetched ${logs.length} logs from block ${currentStart} to ${currentEnd}`);
if (logs.length === 0) {
// No logs in this chunk, move to next
currentStart = currentEnd + 1;
} else {
// Continue from the last block number to avoid missing logs at the boundary
const lastBlock = parseInt(logs[logs.length - 1].blockNumber, 16);
currentStart = lastBlock + 1;
}
}
return allLogs;
}
// Example: fetch logs from block 16,000,000 to 16,100,000 with 10,000 block chunks
fetchAllLogs(16000000, 16100000, 10000).then(logs => {
console.log(`Total logs fetched: ${logs.length}`);
}).catch(err => console.error(err));Tabla de resultados: Mide los límites de tu proveedor
Ejecuta el script anterior con diferentes tamaños de fragmento y registra los resultados. Esto te ayudará a comprender el comportamiento de tu proveedor y ajustar tu estrategia de paginación. La tabla a continuación es para que la completes.
| Tamaño del fragmento (bloques) | Número de registros devueltos | ¿Error o límite alcanzado? | Tiempo empleado (s) |
|-------------------------------|-------------------------------|----------------------------|---------------------|
| 10,000 | | | |
| 5,000 | | | |
| 1,000 | | | |Lista de verificación de solución de problemas: Errores comunes y correcciones
Incluso los desarrolladores experimentados cometen errores al usar eth_getLogs. Aquí tienes una lista de verificación de problemas comunes y cómo solucionarlos.
- ¿Resultado vacío? Verifica que especificaste
fromBlockytoBlock. Si se omiten, el valor predeterminado eslatestpara ambos, lo que devuelve solo registros del bloque más reciente. - ¿Hash de tema incorrecto? Asegúrate de que la firma del evento sea exactamente como se declaró, incluidos los tipos de parámetros y sin espacios. Usa
keccak256para calcular el hash. Por ejemplo,Transfer(address,address,uint256)noTransfer(address, address, uint256). - ¿La dirección no coincide? Recuerda que los parámetros de dirección indexados se rellenan a la izquierda hasta 32 bytes. El tema debe ser
0x000000000000000000000000seguido de la dirección de 20 bytes (sin0x). - ¿Demasiados resultados? Reduce el rango de bloques o usa temas más específicos. Si recibes un error como 'query returned more than 10000 results', debes paginar.
- ¿Faltan registros después de una reorganización? Verifica el indicador
removed. Siremoved: true, el registro ya no es válido y debe descartarse. - ¿Filtrado de parámetros no indexados? No puedes filtrar por parámetros no indexados. Recupera los registros y decodifica el campo
datausando un decodificador ABI. - ¿Límites específicos del proveedor? Consulta la documentación de tu proveedor para conocer el rango máximo de bloques y el número de registros. Estos límites están documentados / varían según el proveedor.
Limitaciones y compensaciones: Indexado vs no indexado, almacenamiento vs recuperación
Al diseñar contratos inteligentes, debes decidir qué parámetros de evento marcar como indexed. Los parámetros indexados permiten un filtrado eficiente pero se limitan a tres por evento e incurren en costos de gas adicionales. Los parámetros no indexados son más baratos pero no se pueden filtrar en cadena; debes recuperarlos y decodificarlos. Esta compensación es crítica para aplicaciones que dependen de registros de eventos para indexación o análisis.
Otra limitación es que eth_getLogs no garantiza un orden global entre bloques. Si necesitas procesar registros en orden, debes ordenarlos por blockNumber y logIndex. Además, los registros no se almacenan permanentemente en cadena; se eliminan de los nodos completos después de un cierto período a menos que uses un nodo de archivo. Para consultas históricas, necesitas un nodo de archivo, como se explica en nuestra guía sobre consultar el estado histórico de Ethereum a través de RPC.
Para monitoreo en tiempo real, eth_newFilter y eth_getFilterChanges son más eficientes que sondear eth_getLogs porque solo devuelven nuevos registros desde la última consulta. Sin embargo, los filtros tienen estado y pueden ser eliminados por el nodo después de un período de inactividad. Para consultas históricas únicas, eth_getLogs es la herramienta adecuada.
Finalmente, ten en cuenta que eth_getLogs puede ser costoso en el lado del nodo. Si realizas muchas consultas, considera usar un proveedor RPC dedicado como el servicio de API de OnFinality para manejar la carga. Consulta nuestros precios de RPC para más detalles.
- Parámetros indexados: hasta 3, filtrables, mayor costo de gas.
- Parámetros no indexados: ilimitados, no filtrables, menor costo de gas.
- Usa
eth_newFilterpara monitoreo continuo,eth_getLogspara consultas únicas. - Se requieren nodos de archivo para registros históricos más allá de la ventana de poda.
Próximos pasos: Lecturas adicionales y guías relacionadas
Ahora que comprendes eth_getLogs, puedes explorar temas relacionados para profundizar tu conocimiento de RPC de Ethereum. Por ejemplo, aprende a simular llamadas a contratos con anulación de estado y simulación con eth_call, o comprende cómo gestionar nonces con gestión de nonces EVM con eth_getTransactionCount. Si te preocupa la latencia, lee nuestra guía sobre latencia y rendimiento de RPC de Ethereum.
Para una perspectiva más amplia sobre elegir el nodo RPC adecuado, consulta Cómo elegir un nodo RPC de Ethereum (Asistente RPC). Y si necesitas consultar datos históricos, nuestra guía sobre consultar el estado histórico de Ethereum a través de RPC es esencial.
Recuerda siempre probar tus consultas contra un endpoint RPC confiable. OnFinality proporciona servicios RPC de Ethereum robustos; consulta nuestro servicio de API para más información.