Logo
Nuevos usuarios de RPC: 35% de descuento el primer mesVer oferta
OnFinality Learn
Guías de red y protocolo14 min de lectura

Recuperación de bloques de Ethereum: transacciones completas, hashes y uncles

Un análisis técnico profundo de eth_getBlockByNumber, eth_getBlockByHash, los métodos de conteo de transacciones y la superficie obsoleta de uncles, con ejemplos ejecutables en Node.js y un método de medición reproducible.

TL;DR

Ethereum JSON-RPC expone dos métodos principales de recuperación de bloques — eth_getBlockByNumber y eth_getBlockByHash — cada uno acepta un parámetro booleano fullTx que selecciona entre un arreglo de objetos de transacción completos y un arreglo de hashes de transacción de 32 bytes. La forma compacta de hashes es drásticamente más pequeña y es el valor predeterminado correcto para indexadores que obtienen transacciones por separado por hash, mientras que la forma completa es útil para el procesamiento de bloques en una sola pasada. Los métodos de conteo de transacciones (eth_getBlockTransactionCountByNumber y eth_getBlockTransactionCountByHash) devuelven solo el conteo, lo que permite verificaciones de cobertura baratas sin transferir el cuerpo del bloque. Los métodos legacy de uncle/ommer (eth_getUncleByBlockNumberAndIndex, eth_getUncleCountByBlockNumber, eth_getUncleByBlockHashAndIndex, eth_getUncleCountByBlockHash) devuelven null o 0x0 para bloques posteriores al Merge porque los bloques de Ethereum ya no llevan uncles, pero los parsers aún deben manejar los campos para bloques históricos previos al Merge y algunas cadenas EVM que no son Ethereum. Este artículo proporciona ejemplos ejecutables en Node.js, un método de medición reproducible y un manual de solución de problemas para la recuperación de bloques vía RPC.

Métodos de recuperación de bloques y el booleano fullTx

La especificación JSON-RPC de Ethereum define dos métodos principales para recuperar un bloque: eth_getBlockByNumber(blockParameter, fullTx) y eth_getBlockByHash(blockHash, fullTx). Ambos aceptan un parámetro booleano fullTx que controla la representación de la lista de transacciones del bloque. Cuando fullTx es true, la respuesta incluye un arreglo de objetos de transacción completos; cuando es false, incluye un arreglo de hashes de transacción de 32 bytes. Este booleano es el parámetro más importante para controlar el tamaño de la respuesta y la lógica de procesamiento posterior.

La forma de hashes es drásticamente más pequeña porque cada hash de transacción tiene exactamente 32 bytes, mientras que un objeto de transacción completo incluye campos como from, to, value, gas, gasPrice, input, v, r, s y potencialmente accessList o maxFeePerGas. Para un bloque con cientos de transacciones, la diferencia puede ser de órdenes de magnitud en el tamaño del payload. Los indexadores que obtendrán transacciones por separado por hash deberían usar fullTx=false de forma predeterminada para evitar transferir los mismos datos dos veces.

Las dos formas son consistentes: cada hash en la forma compacta corresponde a un objeto completo que eth_getTransactionByHash devuelve con los mismos campos. Sin embargo, durante una reorganización, el bloque identificado por un hash dado puede ser reemplazado y la lista de transacciones puede desviarse. Fijar un número de bloque numérico hace que las lecturas sean reproducibles porque el número de bloque es una coordenada estable, mientras que 'latest' es un objetivo móvil.

  • eth_getBlockByNumber(blockParameter, fullTx) — blockParameter puede ser un número de bloque en hexadecimal o una etiqueta nombrada: 'latest', 'pending', 'safe', 'finalized'.
  • eth_getBlockByHash(blockHash, fullTx) — blockHash es un hash de 32 bytes; fullTx controla la representación de las transacciones.
  • fullTx=true devuelve un arreglo de objetos de transacción completos; fullTx=false devuelve un arreglo de hashes de transacción de 32 bytes.
  • La forma de hashes es el valor predeterminado correcto para los indexadores que obtienen transacciones por separado por hash.

Etiquetas de bloque nombradas: latest, pending, safe y finalized

El argumento blockParameter acepta etiquetas nombradas además de números de bloque numéricos. 'latest' se refiere al bloque más reciente conocido por el nodo, 'pending' se refiere a un bloque que aún no está finalizado (y puede que no exista como bloque canónico), 'safe' se refiere a un bloque que está justificado según las reglas de consenso, y 'finalized' se refiere a un bloque que es irreversible según las reglas de consenso. Bajo proof-of-stake, 'safe' y 'finalized' tienen significados específicos definidos por la capa de consenso: 'safe' es el último checkpoint justificado, mientras que 'finalized' es el último checkpoint finalizado.

La diferencia entre 'safe' y 'finalized' importa para las aplicaciones que requieren diferentes niveles de garantía. Es muy poco probable que un bloque 'safe' se reorganice, pero no se garantiza que sea irreversible; un bloque 'finalized' se considera irreversible según el mecanismo de finalidad del protocolo. Para lecturas reproducibles, es preferible fijar un número de bloque numérico porque elimina la ambigüedad sobre qué bloque considera el nodo como 'latest' en el momento de la solicitud.

No todos los clientes admiten las etiquetas 'safe' y 'finalized'. Este comportamiento está documentado / varía según el cliente. Si un cliente no admite estas etiquetas, puede devolver un error o recurrir a 'latest'. Siempre revise la documentación del cliente y pruebe la etiqueta contra su endpoint antes de confiar en ella en producción.

  • 'latest' — bloque más reciente conocido por el nodo; puede cambiar entre solicitudes.
  • 'pending' — un bloque que aún no está finalizado; puede que no exista como bloque canónico.
  • 'safe' — último checkpoint justificado bajo PoS; muy poco probable que se reorganice, pero no irreversible.
  • 'finalized' — último checkpoint finalizado bajo PoS; se considera irreversible.
  • Fijar un número de bloque numérico hace que las lecturas sean reproducibles y elimina la ambigüedad.

Métodos de conteo de transacciones para verificaciones de cobertura baratas

Los métodos eth_getBlockTransactionCountByNumber y eth_getBlockTransactionCountByHash devuelven solo el número de transacciones de un bloque, sin transferir el cuerpo del bloque. Esto es útil para verificaciones de cobertura baratas: un indexador puede verificar que ha procesado el número esperado de transacciones de un bloque sin descargar la lista completa de transacciones. El conteo se devuelve como una cadena hexadecimal, consistente con otros valores numéricos de JSON-RPC.

Estos métodos son particularmente valiosos cuando se combinan con la forma de hashes de recuperación de bloques. Un pipeline puede obtener el bloque con fullTx=false para obtener los hashes de transacción, luego usar eth_getBlockTransactionCountByNumber para confirmar que el conteo coincide con la longitud del arreglo de hashes. Si los conteos divergen, el bloque puede haber sido reorganizado o el proveedor puede haber truncado la respuesta.

Para la recuperación de recibos en bloque, eth_getBlockReceipts devuelve todos los recibos de un bloque en una sola llamada, lo que es más eficiente que obtener los recibos uno por uno. El artículo eth_getBlockReceipts bulk receipts cubre ese método en detalle.

  • eth_getBlockTransactionCountByNumber(blockParameter) — devuelve el conteo de transacciones de un bloque identificado por número o etiqueta.
  • eth_getBlockTransactionCountByHash(blockHash) — devuelve el conteo de transacciones de un bloque identificado por hash.
  • Use estos métodos para verificar que la longitud del arreglo de hashes de transacción coincida con el conteo esperado.
  • Combínelos con eth_getBlockReceipts para una recuperación eficiente de recibos en bloque.

Métodos legacy de uncle y comportamiento posterior al Merge

Los métodos legacy de uncle/ommer son eth_getUncleByBlockNumberAndIndex, eth_getUncleCountByBlockNumber, eth_getUncleByBlockHashAndIndex y eth_getUncleCountByBlockHash. Estos métodos se diseñaron para Ethereum proof-of-work, donde los bloques podían hacer referencia a bloques uncle (también llamados ommers) que eran válidos pero no formaban parte de la cadena canónica. Desde el Merge en septiembre de 2022, los bloques de Ethereum ya no llevan uncles, por lo que estos métodos devuelven null o 0x0 para bloques posteriores al Merge.

Sin embargo, un parser aún debe manejar los campos porque los bloques históricos previos al Merge y algunas cadenas EVM que no son Ethereum sí tienen uncles. El objeto Block incluye un arreglo 'uncles' y un campo 'sha3Uncles'. El arreglo 'uncles' contiene los hashes de los bloques uncle, y 'sha3Uncles' es el hash Keccak-256 de la lista de uncles. Un arreglo 'uncles' vacío no prueba por sí solo que un bloque sea posterior al Merge; para estar seguro, fije un número de bloque superior al bloque del Merge. El esquema de execution-apis de Ethereum documenta la eliminación de uncles posterior al Merge.

Los métodos de uncle están obsoletos y deben tratarse como una superficie legacy de solo lectura, nunca como una fuente de nuevos datos de consenso. Para bloques previos al Merge, siguen siendo útiles para análisis históricos. Para bloques posteriores al Merge, son efectivamente no-ops. La especificación JSON-RPC de Ethereum documenta estos métodos y sus parámetros.

  • eth_getUncleByBlockNumberAndIndex(blockParameter, index) — devuelve el bloque uncle en el índice dado, o null si no hay ninguno.
  • eth_getUncleCountByBlockNumber(blockParameter) — devuelve el número de uncles de un bloque, o 0x0 si no hay ninguno.
  • eth_getUncleByBlockHashAndIndex(blockHash, index) — devuelve el bloque uncle en el índice dado para un bloque identificado por hash.
  • eth_getUncleCountByBlockHash(blockHash) — devuelve el número de uncles de un bloque identificado por hash.
  • Los bloques posteriores al Merge devuelven null o 0x0; los bloques previos al Merge y algunas cadenas EVM que no son Ethereum pueden tener uncles.

Ejemplo ejecutable en Node.js: comparación de representaciones completa y de hashes

El siguiente ejemplo en Node.js obtiene el mismo bloque en ambas representaciones, verifica que la longitud del arreglo de hashes sea igual a la longitud del arreglo completo y comprueba que cada hash coincida con el hash de la transacción completa correspondiente. Utiliza la API fetch integrada disponible en Node.js 18 y posteriores. Reemplace RPC_URL con su endpoint, como un nodo RPC de Ethereum de OnFinality.

Este ejemplo demuestra la consistencia entre las dos formas y proporciona una base para construir indexadores que usan la forma compacta para el almacenamiento y la forma completa para el procesamiento. La lógica de aserción se puede extender para verificar otros campos, como el número de bloque y el hash del padre, para detectar reorganizaciones.

const RPC_URL = 'https://your-ethereum-rpc-endpoint';

async function rpcCall(method, params) {
  const response = await fetch(RPC_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params })
  });
  const data = await response.json();
  if (data.error) throw new Error(data.error.message);
  return data.result;
}

async function compareBlockRepresentations(blockNumberHex) {
  const [blockWithHashes, blockWithFullTx] = await Promise.all([
    rpcCall('eth_getBlockByNumber', [blockNumberHex, false]),
    rpcCall('eth_getBlockByNumber', [blockNumberHex, true])
  ]);

  const hashes = blockWithHashes.transactions;
  const fullTxs = blockWithFullTx.transactions;

  console.log('Block number:', blockWithHashes.number);
  console.log('Hash array length:', hashes.length);
  console.log('Full tx array length:', fullTxs.length);

  if (hashes.length !== fullTxs.length) {
    throw new Error('Length mismatch: ' + hashes.length + ' vs ' + fullTxs.length);
  }

  for (let i = 0; i < hashes.length; i++) {
    if (hashes[i] !== fullTxs[i].hash) {
      throw new Error('Hash mismatch at index ' + i + ': ' + hashes[i] + ' vs ' + fullTxs[i].hash);
    }
  }

  console.log('All hashes match corresponding full transaction hashes.');
  return { hashes, fullTxs };
}

// Example: fetch a specific block by number
compareBlockRepresentations('0x112A880').catch(console.error);

Método reproducible: comparación de tamaños de bloque entre representaciones

Para medir la diferencia de tamaño entre las representaciones completa y de hashes, puede obtener el mismo bloque con fullTx=true y fullTx=false, serializar cada respuesta a JSON y comparar las longitudes en bytes. Este método es reproducible entre endpoints y números de bloque. Los resultados variarán según el bloque, por lo que es útil muestrear varios bloques y registrar los resultados en una tabla.

El siguiente fragmento de Node.js obtiene un bloque en ambas representaciones, mide la longitud de la cadena JSON e imprime la proporción. Puede ejecutarlo contra su propio endpoint y completar la tabla de resultados a continuación. La medición es determinista para un bloque y endpoint dados, pero los tamaños absolutos dependen del número de transacciones y del tamaño de los datos de entrada de cada transacción.

Use un número de bloque numérico en lugar de 'latest' para garantizar la reproducibilidad. Si compara entre proveedores, use el mismo número de bloque y la misma configuración de fullTx. Tenga en cuenta que algunos proveedores pueden limitar o truncar bloques extremadamente grandes, lo que puede afectar la medición.

async function measureBlockSize(blockNumberHex) {
  const [blockWithHashes, blockWithFullTx] = await Promise.all([
    rpcCall('eth_getBlockByNumber', [blockNumberHex, false]),
    rpcCall('eth_getBlockByNumber', [blockNumberHex, true])
  ]);

  const hashesJson = JSON.stringify(blockWithHashes);
  const fullTxJson = JSON.stringify(blockWithFullTx);

  const hashesBytes = Buffer.byteLength(hashesJson, 'utf8');
  const fullTxBytes = Buffer.byteLength(fullTxJson, 'utf8');

  console.log('Block:', blockNumberHex);
  console.log('Hashes form bytes:', hashesBytes);
  console.log('Full tx form bytes:', fullTxBytes);
  console.log('Ratio (full/hashes):', (fullTxBytes / hashesBytes).toFixed(2));

  return { blockNumberHex, hashesBytes, fullTxBytes, ratio: fullTxBytes / hashesBytes };
}

// Example: measure a block
measureBlockSize('0x112A880').catch(console.error);

Tabla de resultados para mediciones del lector

Use la tabla a continuación para registrar sus propias mediciones. Ejecute el fragmento de medición contra su endpoint para varios números de bloque, incluyendo una mezcla de bloques de baja y alta actividad. Las columnas de la tabla capturan el número de bloque, el número de transacciones, el tamaño en bytes de la forma de hashes, el tamaño en bytes de la forma de transacción completa y la proporción. Esto le ayudará a comprender las compensaciones de almacenamiento y ancho de banda para su caso de uso específico.

Debido a que los resultados dependen del bloque y del proveedor, no hay un valor único correcto. El objetivo es establecer una línea base para su propia infraestructura. Si utiliza el servicio API de OnFinality, puede ejecutar estas mediciones contra su endpoint dedicado. Para consideraciones de precios, consulte precios de RPC.

  • Número de bloque | Conteo de transacciones | Bytes forma hashes | Bytes forma tx completa | Proporción (completa/hashes)
  • 0x112A880 | (completar) | (completar) | (completar) | (completar)
  • 0x112A881 | (completar) | (completar) | (completar) | (completar)
  • 0x112A882 | (completar) | (completar) | (completar) | (completar)
  • Agregue filas para bloques adicionales según sea necesario.

Solución de problemas de recuperación de bloques vía RPC

Cuando la recuperación de bloques falla o devuelve resultados inesperados, la causa suele ser uno de algunos problemas comunes. Primero, verifique que el parámetro de bloque esté formateado correctamente: los números de bloque numéricos deben ser cadenas codificadas en hexadecimal (por ejemplo, '0x112A880'), y las etiquetas nombradas deben estar en minúsculas. Segundo, verifique que el booleano fullTx sea un booleano, no una cadena. Tercero, confirme que el bloque exista en la cadena que sirve su endpoint; un número de bloque superior a la punta de la cadena devolverá null.

Si recibe un resultado null para un bloque que debería existir, el nodo puede estar detrás de la punta de la cadena. El artículo Detección de un nodo RPC detrás de la punta de la cadena cubre métodos para diagnosticar el retraso. Si está usando las etiquetas 'safe' o 'finalized' y recibe un error, es posible que el cliente no admita esas etiquetas; recurra a 'latest' o a un número de bloque numérico. Si el conteo de transacciones de eth_getBlockTransactionCountByNumber no coincide con la longitud del arreglo de transacciones, el bloque puede haber sido reorganizado o el proveedor puede haber truncado la respuesta.

Para problemas relacionados con reorganizaciones, el artículo Detección de reorganizaciones de bloques de Ethereum y profundidad RPC proporciona un tratamiento más profundo. Para la reconciliación de indexadores, consulte Reconciliación de indexadores EVM bloque por bloque.

  • Asegúrese de que los parámetros de bloque sean cadenas codificadas en hexadecimal o etiquetas nombradas válidas.
  • Verifique que fullTx sea un booleano, no una cadena.
  • Compruebe que el bloque exista en la cadena servida por su endpoint.
  • Si 'safe' o 'finalized' falla, es posible que el cliente no admita esas etiquetas.
  • Los conteos de transacciones que no coinciden pueden indicar una reorganización o truncamiento del proveedor.

Limitaciones y compensaciones

Se aplican varias limitaciones a la recuperación de bloques vía JSON-RPC. Algunos proveedores limitan o truncan bloques extremadamente grandes, lo que puede resultar en listas de transacciones incompletas. Las etiquetas 'safe' y 'finalized' no son compatibles con todos los clientes; este comportamiento está documentado / varía según el cliente. Los métodos de uncle están obsoletos y deben tratarse como una superficie legacy de solo lectura, nunca como una fuente de nuevos datos de consenso.

La representación de transacción completa es conveniente pero puede ser costosa en términos de ancho de banda y almacenamiento. La representación de hashes es compacta pero requiere llamadas adicionales para obtener los detalles de las transacciones. La elección depende de su caso de uso: si necesita procesar cada transacción de un bloque de inmediato, la forma completa puede ser más simple; si está construyendo un indexador que almacena transacciones por separado, la forma de hashes es más eficiente.

Para una visión más amplia de la configuración y el uso de nodos RPC de Ethereum, consulte la guía de nodos RPC de Ethereum (RPC Assistant). Para más artículos sobre temas de red y protocolo, visite el centro de aprendizaje de OnFinality.

  • Los límites o truncamientos del proveedor pueden afectar bloques extremadamente grandes.
  • Las etiquetas 'safe' y 'finalized' no son universalmente compatibles.
  • Los métodos de uncle están obsoletos y devuelven null o 0x0 después del Merge.
  • La forma de transacción completa aumenta los costos de ancho de banda y almacenamiento.
  • La forma de hashes requiere llamadas adicionales para obtener los detalles de las transacciones.

Próximos pasos para construir pipelines de recuperación de bloques robustos

Para construir un pipeline de recuperación de bloques robusto, comience fijando números de bloque numéricos para lecturas reproducibles. Use la forma de hashes como predeterminada para los indexadores y obtenga transacciones completas solo cuando sea necesario. Implemente verificaciones de consistencia comparando los conteos de transacciones de eth_getBlockTransactionCountByNumber con la longitud del arreglo de transacciones. Maneje las reorganizaciones monitoreando los hashes de los padres y usando una profundidad de confirmación adecuada para su aplicación.

Para indexación de alto rendimiento, considere usar eth_getBlockReceipts para obtener recibos en bloque y combínelo con la forma de hashes para minimizar el tamaño del payload. Si ejecuta su propio nodo, asegúrese de que no esté detrás de la punta de la cadena monitoreando los números de bloque. Para infraestructura gestionada, el nodo RPC de Ethereum y el servicio API de OnFinality proporcionan endpoints que admiten estos métodos.

Finalmente, mantenga los métodos de uncle obsoletos en su parser por compatibilidad histórica, pero no dependa de ellos para nuevos datos de consenso. El repositorio Ethereum execution-apis documenta el esquema y la eliminación de uncles posterior al Merge. Para detalles de precios y planes, consulte precios de RPC.

  • Fije números de bloque numéricos para lecturas reproducibles.
  • Use fullTx=false de forma predeterminada para indexadores; obtenga transacciones completas solo cuando sea necesario.
  • Use eth_getBlockTransactionCountByNumber para verificaciones de cobertura baratas.
  • Monitoree los hashes de los padres y la profundidad de confirmación para manejar reorganizaciones.
  • Mantenga los métodos de uncle por compatibilidad histórica, pero no dependa de ellos para nuevos datos.

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