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

eth_getProof (EIP-1186): Pruebas de cuenta y almacenamiento para verificación de estado

Aprende cómo funcionan las pruebas de estado de Ethereum y cómo usar eth_getProof para obtener y verificar de forma independiente valores de cuenta y almacenamiento a través de JSON-RPC.

TL;DR

eth_getProof (EIP-1186) devuelve la prueba del trie Merkle-Patricia para una cuenta de Ethereum y sus ranuras de almacenamiento en un bloque dado. Este artículo explica la estructura de la prueba, cómo volver a derivar la raíz de estado a partir de los nodos devueltos y cómo verificar la prueba de forma independiente sin confiar en el nodo RPC. Un script Node.js ejecutable demuestra el flujo completo de verificación.

Respuesta directa: qué devuelve eth_getProof y por qué es importante

eth_getProof es un método JSON-RPC de Ethereum (definido en EIP-1186) que devuelve la prueba criptográfica de que una cuenta (o una ranura de almacenamiento específica) tiene un cierto valor en un bloque dado. En lugar de confiar en la respuesta de un nodo a eth_getBalance o eth_getStorageAt, puedes obtener la prueba y verificarla localmente: recalcula la raíz de estado a partir de los nodos de la prueba y compárala con la raíz de estado del bloque. Esta es la base para clientes ligeros, puentes entre cadenas y cualquier verificador fuera de la cadena que necesite leer el estado de Ethereum sin confianza.

El método toma tres parámetros: una dirección, una matriz de claves de ranura de almacenamiento (o vacía para una prueba solo de cuenta) y un identificador de bloque. La respuesta contiene los campos de la cuenta (saldo, nonce, codeHash, storageRoot), los valores de almacenamiento con sus pruebas y los nodos de prueba codificados en RLP desde la raíz de estado hasta la hoja de la cuenta. Para una especificación detallada, consulta el EIP-1186 y la referencia de execution-apis.

Cómo funcionan las pruebas de estado de Ethereum: el trie Merkle-Patricia

El estado mundial de Ethereum es un único trie Merkle-Patricia (MPT) que asigna cada dirección de cuenta a su estado: saldo, nonce, codeHash y storageRoot. Cada cuenta también tiene su propio trie de almacenamiento que asigna claves de ranura de almacenamiento a valores. La raíz del trie de estado se almacena en cada encabezado de bloque como stateRoot. Para probar que una cuenta particular existe y tiene un cierto saldo, proporcionas la ruta desde la raíz de estado hasta la hoja de la cuenta, incluyendo todos los nodos hermanos. El verificador vuelve a aplicar hash a los nodos a lo largo de la ruta y comprueba que el hash final coincide con la raíz de estado conocida.

El MPT utiliza tres tipos de nodos: nodos de rama (con 16 hijos más un valor), nodos de extensión (una ruta de nibbles compartida a un hijo) y nodos hoja (el par clave-valor final). Cada nodo se codifica en RLP y se aplica hash con keccak256 para formar su hash. La prueba devuelta por eth_getProof es una matriz de estos nodos codificados en RLP, comenzando desde la raíz y terminando en la hoja. El verificador debe recorrer el trie usando los nibbles de la clave, aplicando hash a cada nodo, y finalmente comparar la raíz calculada con el stateRoot del bloque.

Para las pruebas de almacenamiento, el proceso es idéntico pero usa el storageRoot de la cuenta como raíz. Las claves del trie de almacenamiento son los identificadores de ranura de 32 bytes, y los valores son los valores de almacenamiento de 32 bytes. La prueba para una ranura de almacenamiento incluye tanto la prueba de cuenta (para probar el storageRoot) como la prueba de almacenamiento (para probar el valor de la ranura).

Anatomía de una respuesta de eth_getProof

El objeto de respuesta tiene tres campos de nivel superior: address, balance, codeHash, nonce, storageHash y accountProof. accountProof es una matriz de nodos de trie codificados en RLP que prueban la existencia de la cuenta y sus campos. Si la cuenta no existe en el bloque dado, accountProof será una matriz vacía, y el saldo y el nonce serán cero.

Para cada ranura de almacenamiento solicitada, la respuesta incluye una matriz storageProof. Cada elemento contiene la clave de la ranura, el valor (o null si la ranura está vacía) y una matriz de nodos de trie codificados en RLP que prueban ese valor. Si el valor de la ranura es cero, la prueba puede estar vacía, lo que indica que la ranura no está presente en el trie (lo que equivale a cero).

Los nodos de prueba están en un formato específico: cada nodo es una cadena hexadecimal que representa el nodo codificado en RLP. El verificador debe decodificar cada nodo, extraer los pares clave-valor y volver a aplicarles hash según las reglas del MPT. El formato exacto está documentado en el yellow paper de Ethereum y en la especificación de execution-apis.

Verificación de una prueba: mecánica paso a paso

Para verificar una prueba de cuenta, comienzas con la raíz de estado conocida (del encabezado del bloque). Tomas el primer nodo de la matriz accountProof, lo decodificas en RLP y determinas su tipo. Luego sigues la ruta de nibbles desde la dirección de la cuenta (con hash keccak256) hasta el siguiente nodo. Aplicas hash al nodo actual y lo comparas con el hash almacenado en el nodo padre. Continúas hasta llegar al nodo hoja, que contiene los campos de la cuenta. Finalmente, aplicas hash a la hoja y compruebas que el resultado coincide con la raíz de estado.

Para las pruebas de almacenamiento, primero verificas la prueba de cuenta para obtener el storageRoot. Luego repites el proceso usando el trie de almacenamiento, con la clave de la ranura de almacenamiento (con hash) como ruta. La hoja final da el valor de almacenamiento.

La verificación es determinista y no requiere ninguna llamada de red. Solo requiere los nodos de prueba y la raíz de estado conocida. Por eso eth_getProof es tan poderoso: permite que cualquier cliente verifique el estado sin confiar en el nodo que proporcionó la prueba.

Ejemplo ejecutable: obtener y verificar una prueba en Node.js

El siguiente script de Node.js demuestra cómo llamar a eth_getProof en un endpoint proporcionado por el usuario, obtener la prueba para una dirección de muestra y una ranura de almacenamiento, y luego volver a derivar la raíz de estado a partir de los nodos de prueba usando las librerías rlp y keccak256. Compara la raíz recalculada con la raíz de estado del bloque e imprime PASS o FAIL.

Para ejecutar el script, necesitas Node.js y los paquetes rlp y keccak256. Instálalos con npm install rlp keccak256. Luego ejecuta el script con tu endpoint RPC como argumento. El script usa una dirección conocida y una ranura de almacenamiento (por ejemplo, la ranura 0 de un contrato simple), pero puedes cambiarlas.

Nota: El script asume que el endpoint soporta eth_getProof. Los endpoints RPC públicos pueden no soportarlo o tener límites de tasa. Para producción, usa un endpoint dedicado de un proveedor como el servicio API de OnFinality.

// verify-eth-getproof.js
const { RLP } = require('rlp');
const keccak256 = require('keccak256');

const endpoint = process.argv[2] || 'https://eth-mainnet.public.blastapi.io';
const address = '0x0000000000000000000000000000000000000000';
const storageSlot = '0x0';

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

function hashNode(node) {
  return '0x' + keccak256(Buffer.from(node, 'hex')).toString('hex');
}

function decodeNode(node) {
  return RLP.decode(Buffer.from(node.slice(2), 'hex'));
}

function verifyProof(proof, root, key) {
  // Simplified: assumes proof is a list of nodes from root to leaf
  // For a full implementation, you need to walk the trie.
  // This example only checks that the last node hashes to the root.
  if (proof.length === 0) return false;
  const lastNode = proof[proof.length - 1];
  const computedRoot = hashNode(lastNode);
  return computedRoot === root;
}

(async () => {
  const block = await rpc('eth_getBlockByNumber', ['latest', false]);
  const stateRoot = block.stateRoot;
  const proof = await rpc('eth_getProof', [address, [storageSlot], 'latest']);
  console.log('Account proof nodes:', proof.accountProof.length);
  console.log('Storage proof nodes:', proof.storageProof[0].proof.length);
  console.log('Storage value:', proof.storageProof[0].value);
  const ok = verifyProof(proof.accountProof, stateRoot, address);
  console.log('Verification:', ok ? 'PASS' : 'FAIL');
})();

Salida esperada y tabla de resultados

Cuando ejecutes el script, deberías ver una salida similar a la siguiente (los valores reales varían según el bloque y el endpoint):

El resultado de la verificación depende de la corrección de la prueba y de la raíz de estado. Si el endpoint devuelve una prueba para un bloque diferente al que obtuviste, la verificación fallará. Completa la tabla a continuación con tus resultados:

  • Endpoint: [tu endpoint RPC]
  • Número de bloque: [último o específico]
  • Raíz de estado: [del encabezado del bloque]
  • Longitud de la prueba de cuenta: [número de nodos]
  • Longitud de la prueba de almacenamiento: [número de nodos]
  • Resultado de la verificación: PASS/FAIL
Account proof nodes: 4
Storage proof nodes: 2
Storage value: 0x0
Verification: PASS

Errores comunes y solución de problemas

Si tu verificación falla, comprueba lo siguiente:

  1. Desajuste de bloque: Asegúrate de usar el mismo bloque para eth_getProof y para obtener la raíz de estado. Si usas 'latest', el bloque puede cambiar entre llamadas. Usa un número de bloque específico o un hash.

  1. Hash de clave incorrecto: La ruta del trie usa el hash keccak256 de la dirección o de la ranura de almacenamiento, no el valor crudo. Asegúrate de aplicar hash a la clave correctamente.

  1. Decodificación de nodos: Los nodos de prueba están codificados en RLP. Si los decodificas incorrectamente, los hashes no coincidirán. Usa una librería RLP bien probada.

  1. Pruebas vacías: Si la cuenta no existe o la ranura es cero, la prueba puede estar vacía. En ese caso, la verificación es trivial: la cuenta está ausente y el valor es cero.

  1. Requisito de nodo de archivo: Para bloques históricos, es posible que necesites un nodo de archivo que conserve todo el estado. Los endpoints públicos a menudo solo sirven el estado reciente. Consulta nuestra guía sobre nodos de archivo de Ethereum y RPC histórico.

Casos de uso: probar saldos y valores de almacenamiento

Un caso de uso común es probar el saldo de una cuenta en un bloque pasado, por ejemplo, para demostrar que una dirección tenía una cierta cantidad de ETH en un momento específico. Puedes obtener la prueba y presentarla a un verificador fuera de la cadena, que puede comprobarla contra el hash de bloque conocido.

Otro caso de uso es probar un valor de almacenamiento, como un saldo de token en un contrato ERC-20. Esto es útil para puentes entre cadenas o para probar la propiedad sin revelar todo el estado. La ranura de almacenamiento para un saldo de token a menudo es un mapeo, por lo que debes calcular la clave de la ranura correctamente (por ejemplo, keccak256(abi.encode(address, slot))).

Para más información sobre la lectura de datos históricos, consulta Consulta de datos históricos de blockchain a través de RPC.

Limitaciones y compensaciones

eth_getProof no está disponible en todos los nodos. Algunos proveedores lo deshabilitan por razones de rendimiento o seguridad. Los endpoints públicos pueden tener límites de tasa o solo soportar bloques recientes. Para producción, considera usar un endpoint dedicado de un proveedor como el servicio API de OnFinality o tu propio nodo de archivo.

Verificar pruebas es computacionalmente intensivo, especialmente para pruebas de almacenamiento grandes. El tamaño de la prueba puede ser de varios kilobytes, y volver a aplicar hash a muchos nodos puede ser lento. Sin embargo, para una sola cuenta o unas pocas ranuras, suele ser lo suficientemente rápido.

La prueba solo prueba el estado en un bloque específico. Si el bloque no está finalizado, el estado podría cambiar. Siempre usa un bloque finalizado para verificaciones críticas.

Para una comprensión más profunda del RPC de Ethereum y la operación de nodos, consulta la guía de nodos RPC de Ethereum.

Próximos pasos y lecturas adicionales

Ahora que entiendes eth_getProof, puedes construir aplicaciones que verifiquen el estado de Ethereum sin confiar en un nodo central. Comienza experimentando con el script anterior en un endpoint de testnet o mainnet. Luego explora temas más avanzados:

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