Ethereum: de historia a labs

Artículo 12 de 48

25%

completado al llegar aquí

Sistemas distribuidosNota de investigación13 min de lectura

JSON-RPC: hablar con Ethereum sin magia

Cómo es una petición JSON-RPC, qué significan latest, safe y finalized, cómo fijar el bloque de una lectura y qué confianza depositas en un proveedor RPC.

Ruta Ethereum: 0/48

Un panel de tesorería muestra un saldo de 5 ETH. Un minuto después muestra 4 y luego vuelve a 5. Nadie movió fondos: el panel preguntaba por latest a dos nodos distintos detrás de un balanceador, y cada uno veía una punta de cadena diferente. El error no estaba en Ethereum, sino en no decir qué bloque se estaba leyendo.

JSON-RPC es la interfaz con la que casi todo el software habla con un nodo de Ethereum: wallets, frontends, indexadores y scripts. Es un protocolo de llamada remota sin estado que viaja en JSON. La lista de métodos eth_* y sus esquemas están especificados en el repositorio execution-apis.

Brújula de lectura

Actor, evidencia y estado

Antes de avanzar, ubica quién actúa, qué prueba queda y qué parte del sistema cambia.

Actor

Nodo o proveedor RPC

Responde según su propia vista de la cadena y sus propias políticas.

Evidencia

Respuesta fijada a un bloque

Número o hash de bloque, y una prueba Merkle cuando la confianza importa.

Estado

Lectura, no escritura

Solo eth_sendRawTransaction propone un cambio; el resto observa o simula.

Preguntas guía

  • ¿Qué viaja exactamente en una petición y una respuesta JSON-RPC?
  • ¿Qué diferencia hay entre leer en latest, safe y finalized?
  • ¿Cómo haces que una lectura sea reproducible días después?
  • ¿Qué parte de la seguridad de tu dApp depende del proveedor RPC?

Conexiones dentro de la serie

Una petición JSON-RPC por dentro

Cada petición es un objeto JSON con cuatro campos: jsonrpc (siempre "2.0"), method, params y un id que la respuesta repite para emparejarlas.

Bash
# Ilustrativo contra un nodo local (Anvil escucha en 8545 por defecto)
curl -s -X POST http://127.0.0.1:8545 \
  -H "Content-Type: application/json" \
  --data '{"jsonrpc":"2.0","method":"eth_getBalance","params":["0x70997970C51812dc3A010C7d01b50e0d17dc79C8","latest"],"id":1}'
JSON
{"jsonrpc":"2.0","id":1,"result":"0x21e19e0c9bab2400000"}

El resultado es el saldo en wei escrito en hexadecimal: 0x21e19e0c9bab2400000 son 102210^{22} wei, los 10 000 ETH con los que la CLI de Anvil financia por defecto cada cuenta de prueba (opción --balance). Lo importante es el formato. La especificación distingue dos codificaciones hexadecimales. Las cantidades (enteros) van con 0x y sin ceros a la izquierda: cero se escribe 0x0, nunca 0x ni 0x00. Los datos (direcciones, hashes, bytecode) van con 0x y dos dígitos por byte, de modo que 0x vacío es válido y 0xf0f0f no lo es. Un servicio en Java o Rust que parsea cantidades como si fueran datos acaba rechazando respuestas válidas.

Familias de métodos y qué hace cada una

Los métodos eth_* se agrupan por lo que hacen con el estado:

FamiliaMétodos representativosQué hace
Leer estadoeth_getBalance, eth_getCode, eth_getStorageAt, eth_getTransactionCountConsulta campos de una cuenta en un bloque
Simulareth_call, eth_estimateGas, eth_simulateV1Ejecuta sin crear transacción ni cambiar estado
Enviareth_sendRawTransactionEntrega una transacción ya firmada y devuelve su hash
Historiaeth_getBlockByNumber, eth_getTransactionReceipt, eth_getLogsLee bloques, receipts y eventos
Comisioneseth_feeHistory, eth_maxPriorityFeePerGas, eth_blobBaseFeeDatos para fijar maxFee y la propina
Pruebaseth_getProofDevuelve cuenta y slots con su prueba Merkle

Según la especificación, eth_call ejecuta una llamada de inmediato sin crear una transacción en la cadena. Es el modo de leer funciones view y de reproducir un revert para leer su motivo. eth_estimateGas usa el mismo mecanismo para estimar cuánto gas hará falta; como vimos en el capítulo de la EVM, la estimación depende del contexto exacto de ejecución.

Hay dos métodos que un equipo de custodia debe tratar aparte: eth_sendTransaction firma y envía, y eth_sign devuelve una firma EIP-191. Ambos suponen que el nodo guarda la clave privada. En un proveedor compartido no deberían existir; en tu propia infraestructura, la firma debe vivir en un componente separado y auditado, y el nodo solo debe recibir transacciones ya firmadas con eth_sendRawTransaction.

Invariante de lectura: fijar el bloque

Todo método que lee estado acepta un parámetro de bloque. La especificación define cinco etiquetas:

  • earliest: el bloque más bajo que el cliente tiene disponible.
  • finalized: el bloque más reciente con seguridad criptoeconómica; no se puede reorganizar salvo por intervención manual coordinada por la comunidad.
  • safe: el más reciente que está a salvo de reorganizaciones bajo mayoría honesta y ciertos supuestos de sincronía.
  • latest: el más reciente de la cadena canónica que ve el cliente; puede salir de la cadena incluso en condiciones normales.
  • pending: una muestra del siguiente bloque construida por el cliente con transacciones de su mempool.
Figura 1

Etiquetas de bloque: de lo más firme a lo más fresco

Fuentes:

[2]
Etiquetas de bloque: de lo más firme a lo más fresco
Cómo leer esta figura

Una fila de ocho bloques unidos por flechas y un noveno, discontinuo, al final. De izquierda a derecha se marcan: earliest, el más antiguo que el nodo conserva; finalized, en verde, cuya reversión exige intervención manual coordinada por la comunidad; safe, en azul, a salvo de reorganizaciones con mayoría honesta y sincronía; latest, en ocre, que puede reorganizarse en condiciones normales; y pending, discontinuo, una muestra local construida desde el mempool. Debajo, una flecha de doble punta va de más firme, a la izquierda, a más fresco, a la derecha.

Visual original de TrautsLab. Cada etiqueta de bloque intercambia frescura por firmeza. latest es lo último que ve el nodo y puede reorganizarse; safe y finalized van por detrás con garantías más fuertes; pending ni siquiera es un bloque de la cadena.

El invariante útil es este: una lectura sin bloque fijo no es reproducible. Si dos llamadas usan latest, pueden leer bloques distintos, como en el panel del ejemplo. La regla práctica tiene tres pasos: resolver primero el bloque con eth_getBlockByNumber, guardar su número y su hash, y hacer todas las lecturas siguientes contra ese bloque.

Fijar por número no basta si hay una reorganización: el mismo número puede pasar a ser otro bloque. EIP-1898 permite pasar un objeto {"blockHash": ..., "requireCanonical": true} en lugar de un número; con requireCanonical, el nodo debe dar error si ese hash ya no está en la cadena canónica. Para contabilidad y conciliación, lo razonable es leer en finalized; para la interfaz de usuario, latest con un aviso de que el dato puede cambiar.

Cuello de botella: eth_getLogs y los rangos de bloques

eth_getLogs devuelve los logs que coinciden con un filtro de direcciones y topics en un rango de bloques inclusivo. La especificación es estricta: si fromBlock o toBlock quedan por encima de la punta de la cadena, o si fromBlock es mayor que toBlock, el cliente debe responder con el error -32602 y no puede recortar el rango ni devolver un resultado parcial. La misma especificación define para este método el error 4444 con el mensaje Pruned history unavailable, que devuelve un nodo que ya no guarda los bloques pedidos.

En la práctica, los proveedores añaden límites propios de rango y de tamaño de respuesta, y cada uno los documenta a su manera. Un indexador robusto pide rangos pequeños, guarda el último bloque procesado y vuelve a pedir el tramo reciente cuando detecta una reorganización. Cada log trae el campo removed, que vale true cuando una reorganización lo eliminó.

Topología: cliente de ejecución, de consenso y proveedor

Desde The Merge, un nodo de Ethereum son dos programas. El cliente de ejecución recibe transacciones, las ejecuta en la EVM y guarda el estado; es quien sirve JSON-RPC. El cliente de consenso implementa proof-of-stake y le dice al de ejecución qué bloques forman la cadena. Se comunican por la Engine API, que escucha por defecto en el puerto 8551 y exige autenticación JWT. Ese puerto no debe exponerse a internet; el de JSON-RPC, tampoco sin autenticación delante.

El tipo de nodo decide qué preguntas puedes hacer. Un nodo completo conserva el estado de los bloques recientes, típicamente los últimos 128, y puede regenerar estados antiguos bajo demanda; un nodo de archivo guarda todos los estados históricos y es el que hace falta para preguntar con fiabilidad por un saldo de hace años.

Un proveedor RPC es un tercero que opera nodos por ti. Simplifica la operación, pero cada respuesta pasa a depender de su honestidad, de su disponibilidad y de qué nodo concreto atendió la petición. Hay dos defensas que no requieren confiar: comparar respuestas de varios proveedores y verificar datos críticos con eth_getProof contra cabeceras que obtienes por un canal propio.

Lista de verificación

Controles mínimos para un cliente RPC en producción

0 de 6 revisados

Sesión curl contra Anvil: leer sin herramientas mágicas

Este ejercicio usa solo curl y cast rpc, que envía métodos JSON-RPC en crudo. Anvil escucha en el puerto 8545 y usa el chain ID 31337.

Bash
# Terminal 1
anvil
 
# Terminal 2: chain ID (31337 = 0x7a69) y número de bloque
curl -s -X POST http://127.0.0.1:8545 -H "Content-Type: application/json" \
  --data '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}'
curl -s -X POST http://127.0.0.1:8545 -H "Content-Type: application/json" \
  --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":2}'
 
# Lo mismo con cast rpc, y una lectura fijada a un bloque concreto
export ETH_RPC_URL=http://127.0.0.1:8545
cast rpc eth_getBlockByNumber latest false
cast rpc eth_getBalance 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 0x0

Qué observar: eth_chainId devuelve 0x7a69; el número de bloque es una cantidad hexadecimal sin ceros a la izquierda; eth_getBlockByNumber devuelve el hash que usarías para fijar lecturas posteriores. Repite la lectura del saldo en 0x0 después de enviar una transferencia y comprueba que no cambia, mientras que en latest sí lo hace. Esa diferencia es la reproducibilidad del invariante anterior.

Referencias

Bibliografía académica

Formato IEEENumerado para ingeniería, sistemas y protocolos.
  1. [1]

    ethereum.org, "JSON-RPC API," Ethereum developer documentation. [Online]. Available: https://ethereum.org/developers/docs/apis/json-rpc/

    official docsID: ETH-JSON-RPC-DOCSAbrir fuente (abre en otra pestaña)

    Codificación hex de cantidades y datos, parámetro de bloque y ejemplos curl.

  2. [2]

    Ethereum, "Execution APIs: JSON-RPC specification," ethereum/execution-apis, GitHub repository.

    formal specID: ETH-EXECUTION-APISAbrir fuente (abre en otra pestaña)

    Especificación OpenRPC de métodos eth_*, block tags, receipts y logs.

  3. [3]

    Foundry contributors, "anvil/src/cmd.rs," foundry-rs/foundry, GitHub repository.

    source codeID: FOUNDRY-ANVIL-CLIAbrir fuente (abre en otra pestaña)

    Opciones de la CLI de Anvil; --balance vale 10000 ETH por defecto.

  4. [4]

    C. Cooper, "EIP-1898: Add blockHash to defaultBlock methods," Ethereum Improvement Proposals, 2019.

    Consultas de estado fijadas por hash de bloque.

  5. [5]

    ethereum.org, "Nodes and clients," Ethereum developer documentation. [Online]. Available: https://ethereum.org/developers/docs/nodes-and-clients/

    official docsID: ETH-NODES-DOCSAbrir fuente (abre en otra pestaña)

    Clientes de ejecución y consenso; nodos full, archive y light.

  6. [6]

    Ethereum, "Engine API: common definitions," ethereum/execution-apis, GitHub repository.

    Puerto 8551 y autenticación JWT entre cliente de consenso y de ejecución.

  7. [7]

    S. Jentzsch and C. Jentzsch, "EIP-1186: RPC-method to get Merkle proofs - eth_getProof," Ethereum Improvement Proposals, 2018.

    Pruebas Merkle de cuenta y storage por RPC.

  8. [8]

    Foundry contributors, "anvil/src/config.rs," foundry-rs/foundry, GitHub repository.

    source codeID: FOUNDRY-ANVIL-SRCAbrir fuente (abre en otra pestaña)

    Valores por defecto de Anvil: puerto 8545, chain ID 31337 y mnemónica de prueba.

Fuentes primarias, papers seminales y especificaciones técnicas citadas en esta investigación.

Rutas vecinas

Si este tema te abrió hambre, sigue por aquí.

Series y labs que comparten área o dependen de esta ruta. Pocas opciones, para no convertir la curiosidad en menú infinito.

Sigue leyendo

Ideas conectadas con este artículo

Una ruta corta para continuar sin abrir veinte pestañas y perder el hilo.

Los comentarios se cargan al final de la lectura para no meter peso antes de tiempo.