eth_getBlockReceipts(blockParameter) devuelve un arreglo con todos los recibos de transacción de un bloque en un solo viaje de ida y vuelta JSON-RPC, usando las mismas formas de parámetro de bloque que eth_getBlockByNumber (número, etiqueta o hash de 32 bytes). Un recibo es el resumen posterior a la ejecución de una transacción —status, cumulativeGasUsed, gasUsed, effectiveGasPrice, logsBloom, logs, contractAddress y type— y el receiptsRoot del bloque compromete a todos ellos. Iterar eth_getTransactionReceipt una vez por transacción multiplica los viajes de ida y vuelta y la presión sobre los límites de tasa por la cantidad de transacciones, por lo que la obtención en bloque es la mejor opción para indexadores y procesamientos analíticos por bloque. La disponibilidad del método depende del cliente y del proveedor, así que detecta el soporte en tiempo de ejecución y recurre a llamadas por transacción cuando el método en bloque devuelve método no encontrado.
Qué es un recibo y por qué el bloque compromete a todos ellos
Un recibo de transacción es el resumen posterior a la ejecución de una transacción. Te indica si la transacción tuvo éxito o se revirtió, cuánto gas consumió, cuánto pagó por unidad de gas, qué registros emitió y si desplegó un contrato. La especificación JSON-RPC de execution-apis de Ethereum define los campos del objeto de recibo, y el listado de la API JSON-RPC de ethereum.org documenta la misma estructura para consumo público.
Los campos más relevantes para el análisis son status (1 para éxito, 0 para reversión), cumulativeGasUsed (gas usado por todas las transacciones hasta esta inclusive en el bloque), gasUsed (gas usado solo por esta transacción), effectiveGasPrice (el precio real por gas pagado después de EIP-1559), logsBloom (un filtro probabilístico sobre los registros del bloque), logs (los registros de eventos emitidos), contractAddress (se establece cuando la transacción creó un contrato) y type (el tipo de transacción).
Cada encabezado de bloque contiene un receiptsRoot, una raíz Merkle-Patricia que compromete a todos los recibos del bloque. Ese compromiso es la razón por la que los recibos son la prueba canónica de los resultados de ejecución de un bloque: si tienes los recibos, puedes verificar lo que el bloque realmente hizo, no solo qué transacciones contenía. Para una visión más amplia de cómo encajan estas llamadas en la configuración de un nodo, consulta Elegir un nodo RPC de Ethereum.
- status: 1 = éxito, 0 = reversión; el primer campo por el que la mayoría de los indexadores ramifican.
- gasUsed vs cumulativeGasUsed: por transacción vs total acumulado dentro del bloque.
- effectiveGasPrice: el precio real pagado después de EIP-1559, usado para calcular feePaid.
- logs y logsBloom: eventos emitidos y el filtro bloom a nivel de bloque sobre ellos.
- contractAddress: no nulo solo cuando la transacción desplegó un contrato.
- type: tipo de transacción, útil al mezclar transacciones heredadas y tipadas.
eth_getBlockReceipts: una llamada, todos los recibos
eth_getBlockReceipts(blockParameter) devuelve un arreglo con todos los recibos del bloque identificado por blockParameter. El parámetro acepta las mismas formas que eth_getBlockByNumber: un número de bloque, una etiqueta como latest, safe o finalized, o un hash de bloque de 32 bytes. La respuesta es un arreglo ordenado por índice de transacción, por lo que receipt[i] corresponde a la transacción en el índice i del bloque.
El patrón alternativo es obtener el bloque con eth_getBlockByNumber, leer su arreglo transactions y luego llamar a eth_getTransactionReceipt una vez por cada hash de transacción. Eso funciona, pero multiplica los viajes de ida y vuelta y la presión sobre los límites de tasa por la cantidad de transacciones. Un bloque con 200 transacciones se convierte en 1 + 200 llamadas en lugar de 1 + 1. Para un indexador o procesador de bloques, esa diferencia se acumula en cada bloque que procesas.
La disponibilidad del método depende del cliente y del proveedor. Algunos clientes más antiguos y algunos endpoints alojados no exponían eth_getBlockReceipts, y el soporte puede variar según la red y la configuración del proveedor. Trátalo como una capacidad que se detecta en tiempo de ejecución en lugar de una suposición. El centro de aprendizaje de OnFinality cubre guías relacionadas a nivel de método, incluida Filtrar registros de eventos con eth_getLogs y topics, que es la contraparte orientada a registros de la obtención de recibos.
- En bloque: 1 llamada al bloque + 1 llamada a recibos = 2 viajes de ida y vuelta por bloque.
- Por transacción: 1 llamada al bloque + N llamadas a recibos = 1 + N viajes de ida y vuelta por bloque.
- Orden: los recibos siguen el orden del índice de transacción; empareja por índice o por receipt.transactionHash.
- No asumas que los registros están agrupados por contrato o tema; siguen el orden por transacción y por emisión.
Cuándo usar recibos en bloque vs recibos por transacción
Usa eth_getBlockReceipts cuando procesas un bloque completo y necesitas todos los recibos: indexadores, procesamientos analíticos, exploradores de bloques, paneles de uso de gas y rellenos conscientes de reorganizaciones. La llamada en bloque te da el conjunto completo en un solo viaje de ida y vuelta, que es la forma eficiente para el trabajo por bloque.
Usa eth_getTransactionReceipt cuando ya tienes un hash de transacción específico y solo necesitas ese recibo —por ejemplo, confirmar una transacción enviada por un usuario, verificar el despliegue de un solo contrato o sondear un recibo después de enviar una transacción. En ese caso, la llamada por transacción es la herramienta correcta y la llamada en bloque sería un desperdicio.
Un diseño de suscripción a registros o de relleno aún puede superar a ambos para transmisión en tiempo real, porque empuja los eventos a medida que ocurren en lugar de sondear bloques. Los recibos en bloque son un patrón basado en extracción; si tu carga de trabajo es en tiempo real y basada en eventos, una suscripción más un relleno para los huecos suele ser la mejor arquitectura. La guía Trazado de transacciones de Ethereum con los espacios de nombres trace y debug cubre las llamadas de introspección más profundas que están por debajo de los recibos cuando necesitas trazas de llamadas internas.
- Recibos en bloque: procesamiento de bloques completos, análisis, exploradores, rellenos de reorganizaciones.
- Recibos por transacción: confirmación de un solo hash, verificaciones de estado orientadas al usuario.
- Suscripciones/relleno: transmisión de eventos en tiempo real donde el empuje supera a la extracción.
- Trazado: cuando los recibos no son suficientes y necesitas detalle de llamadas internas.
Un patrón práctico de procesamiento de bloques: obtener, unir, emitir
El patrón es sencillo: para cada bloque, llama a eth_getBlockByNumber con withTransactions=true y a eth_getBlockReceipts en paralelo, luego une por índice. A partir de los datos unidos puedes emitir filas por transacción (status, gasUsed, effectiveGasPrice, feePaid = gasUsed * effectiveGasPrice, cantidad de registros, creación de contrato) y agregados por bloque (gas total usado, comisiones totales, conteos de éxito/reversión, cantidad de registros).
Unir por índice es el enfoque más simple, pero también debes verificar con receipt.transactionHash contra block.transactions[i].hash. Si los dos no coinciden, probablemente estés ante una reorganización o un parámetro de bloque desajustado, y deberías volver a obtener en lugar de emitir filas incorrectas.
Este patrón supera a N llamadas de recibos para un procesamiento analítico porque mantiene los viajes de ida y vuelta constantes por bloque independientemente de la cantidad de transacciones. Aun así, pierde frente a un diseño de suscripción a registros/relleno para tiempo real, porque sondea en lugar de transmitir. Para la selección de endpoints y el estado, consulta Monitoreo de endpoints RPC y estado del nodo.
- Paraleliza las llamadas al bloque y a los recibos para reducir el tiempo de reloj.
- Une por índice y luego verifica con receipt.transactionHash.
- Emite filas por transacción y agregados por bloque a partir del conjunto unido.
- Vuelve a obtener cuando haya discrepancia de hash en lugar de emitir filas inconsistentes.
Node.js ejecutable: obtener un bloque y sus recibos, unir, imprimir una tabla
El script a continuación usa el fetch integrado disponible en Node.js moderno. Llama a eth_getBlockByNumber y eth_getBlockReceipts en paralelo, une los recibos con las transacciones por índice e imprime una tabla de estado, gas usado, precio efectivo del gas, comisión pagada, cantidad de registros y creación de contrato. Reemplaza la URL de RPC con tu propio endpoint.
Ejecútalo con node script.js. Si tu endpoint no admite eth_getBlockReceipts, el script mostrará el error y podrás cambiar al patrón alternativo descrito en la siguiente sección.
const RPC_URL = process.env.RPC_URL || 'https://your-endpoint.example';
async function rpc(method, params) {
const res = await fetch(RPC_URL, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params })
});
const json = await res.json();
if (json.error) throw new Error(`${method}: ${json.error.message}`);
return json.result;
}
function hexToBigInt(hex) {
return hex ? BigInt(hex) : 0n;
}
async function processBlock(blockParam) {
const [block, receipts] = await Promise.all([
rpc('eth_getBlockByNumber', [blockParam, true]),
rpc('eth_getBlockReceipts', [blockParam])
]);
if (!block) throw new Error('Block not found: ' + blockParam);
if (!receipts) throw new Error('No receipts returned for ' + blockParam);
const rows = receipts.map((r, i) => {
const tx = block.transactions[i];
const gasUsed = hexToBigInt(r.gasUsed);
const effGasPrice = hexToBigInt(r.effectiveGasPrice);
const feePaid = gasUsed * effGasPrice;
return {
index: i,
hash: r.transactionHash,
matchesBlockTx: tx && tx.hash.toLowerCase() === r.transactionHash.toLowerCase(),
status: parseInt(r.status, 16),
gasUsed: gasUsed.toString(),
effectiveGasPrice: effGasPrice.toString(),
feePaidWei: feePaid.toString(),
logCount: r.logs ? r.logs.length : 0,
contractCreated: r.contractAddress || null
};
});
const totalGas = rows.reduce((a, r) => a + BigInt(r.gasUsed), 0n);
const totalFees = rows.reduce((a, r) => a + BigInt(r.feePaidWei), 0n);
const reverts = rows.filter(r => r.status === 0).length;
console.log(`Block ${block.number} txs=${rows.length} reverts=${reverts}`);
console.log(`totalGasUsed=${totalGas} totalFeesWei=${totalFees}`);
console.table(rows.map(r => ({
i: r.index,
status: r.status,
gasUsed: r.gasUsed,
effGasPrice: r.effectiveGasPrice,
feePaidWei: r.feePaidWei,
logs: r.logCount,
contract: r.contractCreated ? 'yes' : ''
})));
}
processBlock('latest').catch(err => {
console.error('Failed:', err.message);
process.exit(1);
});Detectar soporte y recurrir a recibos por transacción
Como eth_getBlockReceipts no está disponible universalmente, detecta el soporte en tiempo de ejecución. El enfoque más limpio es intentar la llamada en bloque y capturar un error de método no encontrado, luego recurrir a llamadas de recibo por transacción. Almacena en caché el resultado de la capacidad para no pagar el costo de detección en cada bloque.
La alternativa emite eth_getTransactionReceipt una vez por cada hash de transacción del arreglo transactions del bloque. Ese es el patrón 1 + N, y es correcto pero más costoso. Si tu endpoint carece sistemáticamente del método en bloque, considera si un endpoint o proveedor diferente se ajusta mejor a tu carga de trabajo; las páginas de Precios de RPC y Servicio de API describen cómo OnFinality estructura el acceso, y Redes de Ethereum enumera los endpoints de red disponibles.
Una alternativa robusta también debe manejar fallos parciales: si una llamada por transacción falla, reinténtala con retroceso exponencial en lugar de descartar todo el bloque. Registra qué bloques usaron la alternativa para poder revisarlos si luego cambias de endpoint.
- Intenta primero el método en bloque; captura método no encontrado y cambia a llamadas por transacción.
- Almacena en caché el resultado de la capacidad por endpoint para evitar detecciones repetidas.
- Reintenta fallos individuales por transacción con retroceso en lugar de descartar el bloque.
- Registra el uso de la alternativa para poder auditar la capacidad del endpoint con el tiempo.
Medir el costo contra tu propio endpoint: una tabla de resultados para completar
No confíes en afirmaciones genéricas de rendimiento; mide contra tu propio endpoint. La comparación es simple: los recibos en bloque cuestan 1 llamada al bloque + 1 llamada a recibos por bloque, mientras que los recibos por transacción cuestan 1 llamada al bloque + N llamadas a recibos, donde N es la cantidad de transacciones. La relación de viajes de ida y vuelta es aproximadamente (1 + N) / 2, que crece con el tamaño del bloque.
Ejecuta el mismo bloque con ambos patrones y registra el tiempo de reloj, la cantidad de solicitudes y cualquier respuesta de límite de tasa. Completa la tabla a continuación con tus propias mediciones. La latencia y los límites de tasa específicos del proveedor están documentados / varían según el proveedor, así que tus números son los que importan para la planificación de capacidad.
Una medición secundaria útil es los bytes transferidos: la respuesta en bloque contiene todos los recibos en una sola carga útil, que puede ser más grande que las respuestas individuales pero evita la sobrecarga por llamada. Vigila los límites de tamaño de respuesta del proveedor en bloques muy grandes.
- Viajes de ida y vuelta (en bloque): 2 por bloque, independientemente de la cantidad de transacciones.
- Viajes de ida y vuelta (por transacción): 1 + N por bloque, escalando con la cantidad de transacciones.
- Mide: ms de reloj, cantidad de solicitudes, errores de límite de tasa, bytes de respuesta.
- Registra el número de bloque y la cantidad de transacciones junto a cada medición para comparabilidad.
| Block | Tx count | Pattern | Requests | Wall-clock ms | Rate-limit errors | Response bytes |
|-------|----------|---------|----------|---------------|-------------------|----------------|
| | | bulk | | | | |
| | | per-tx | | | | |
| | | bulk | | | | |
| | | per-tx | | | | |Modos de fallo: método no soportado, bloques nulos, reorganizaciones y límites
Método no soportado: algunos clientes y endpoints alojados devuelven un error de método no encontrado para eth_getBlockReceipts. Manéjalo explícitamente y recurre a llamadas por transacción en lugar de hacer fallar tu pipeline.
Nulo para un bloque pendiente o no disponible: si solicitas un bloque que aún no existe o no está disponible en tu nodo, la llamada puede devolver null. Trata null como una señal para reintentar más tarde o para omitir, no como un conjunto de recibos vacío. Un bloque pendiente también puede no tener recibos todavía.
Recibos de un bloque reorganizado: si ocurre una reorganización, los recibos que obtuviste para el bloque antiguo pueden ya no corresponder a la cadena canónica. Verifica volviendo a obtener el bloque y comparando hashes, y vuelve a procesar el rango afectado. Para redes con semántica de ejecución asíncrona, el momento del estado del recibo puede diferir; consulta Ciclo de vida de transacciones en Monad y estado del recibo para una discusión relacionada.
Usar un hash de bloque para un bloque no finalizado: un hash de bloque identifica un bloque específico, pero si ese bloque luego se reorganiza, el hash puede ya no resolverse. Prefiere etiquetas como finalized para un procesamiento estable, y trata latest o safe como provisionales. Los límites de rango de bloques y tamaño de respuesta del proveedor también pueden causar fallos en bloques muy grandes o rangos amplios; revisa los límites documentados de tu proveedor.
- método no encontrado: recurre a llamadas de recibo por transacción.
- resultado nulo: bloque pendiente o no disponible; reintenta u omite.
- reorganización: vuelve a obtener y reprocesa el rango afectado.
- hash no finalizado: prefiere etiquetas finalized para un procesamiento estable.
- límites del proveedor: los topes de rango de bloques y tamaño de respuesta varían según el proveedor.
Limitaciones y compensaciones: recibos vs registros, requisitos de archivo
Los recibos no sustituyen a eth_getLogs cuando necesitas filtrar por tema o dirección en muchos bloques. eth_getLogs está diseñado para el filtrado de registros y puede escanear rangos de manera eficiente, mientras que los recibos te dan la imagen completa por transacción de un solo bloque. Usa recibos para resultados de ejecución por bloque y registros para consultas de eventos entre bloques.
Los bloques antiguos pueden requerir un nodo de archivo. Si tu endpoint no tiene archivo habilitado, la obtención de recibos históricos puede fallar o devolver errores para bloques fuera del rango podado. Confirma la capacidad de archivo de tu endpoint antes de rellenar el historial.
Compensaciones de seguridad y corrección: los recibos son el resumen canónico de ejecución, pero aún deberías verificar el receiptsRoot cuando necesites garantía criptográfica. Para la mayoría de las cargas de trabajo analíticas, confiar en la respuesta del nodo es aceptable; para sistemas de alta garantía, verifica contra el encabezado del bloque. También ten en cuenta que logsBloom es probabilístico y no sustituye el escaneo de registros.
- Recibos: resultados de ejecución por bloque; registros: filtrado de eventos entre bloques.
- Requisito de archivo: los recibos históricos pueden necesitar un endpoint con archivo habilitado.
- receiptsRoot: verifica cuando necesites garantía criptográfica.
- logsBloom: filtro probabilístico, no un reemplazo del escaneo de registros.
Lista de verificación de solución de problemas para la obtención de recibos en bloque
Cuando eth_getBlockReceipts no se comporta como se espera, revisa las siguientes comprobaciones en orden. La mayoría de los problemas caen en un pequeño conjunto de categorías: soporte del método, disponibilidad del bloque, momento de la reorganización y límites del proveedor.
Comienza confirmando que el método es compatible con tu endpoint con una llamada simple contra latest. Si eso falla con método no encontrado, cambia a la alternativa. Si tiene éxito pero devuelve null, verifica si el parámetro de bloque es válido y si el bloque está disponible en tu nodo. Si los recibos parecen obsoletos o desajustados, verifica si hay una reorganización y vuelve a obtener.
- Confirma el soporte del método con una llamada al bloque latest.
- Valida la forma del parámetro de bloque (número, etiqueta o hash de 32 bytes).
- Verifica resultados nulos y distingue pendiente de no disponible.
- Verifica receipt.transactionHash contra block.transactions[i].hash.
- Vuelve a obtener ante discrepancia para manejar reorganizaciones.
- Revisa los límites de rango de bloques y tamaño de respuesta del proveedor para bloques grandes.
- Confirma la capacidad de archivo para bloques históricos.
- Registra el uso de la alternativa y los errores de límite de tasa para la planificación de capacidad.
Próximos pasos: construir un pipeline basado en recibos
Con los recibos en bloque en su lugar, el siguiente paso es decidir cómo tu pipeline maneja los datos en tiempo real versus los históricos. Para tiempo real, considera un diseño de suscripción a registros con un relleno para los huecos; para análisis históricos, los recibos en bloque por bloque son la forma eficiente. El centro de aprendizaje de OnFinality tiene guías relacionadas sobre registros, trazado y monitoreo que complementan esta.
Si estás eligiendo un endpoint, revisa Elegir un nodo RPC de Ethereum y la página de Redes de Ethereum. Para detalles de acceso y precios, consulta Precios de RPC y la página del Servicio de API. Mide tu propio comportamiento de viajes de ida y vuelta y límites de tasa antes de comprometerte con un diseño, y mantén la ruta alternativa en su lugar para que tu pipeline sobreviva a endpoints que carecen del método en bloque.
- Tiempo real: suscripción más relleno; histórico: recibos en bloque por bloque.
- Mantén la alternativa por transacción para endpoints sin el método en bloque.
- Mide los viajes de ida y vuelta y el comportamiento de límite de tasa contra tu propio endpoint.
- Revisa la selección de endpoint y los precios antes de escalar.