Ethereum: de historia a labs

Artículo 8 de 48

17%

completado al llegar aquí

Sistemas distribuidosNota de investigación17 min de lectura

Una transacción Ethereum por dentro

Campos de una transacción tipo 2, cómo EIP-1559 reparte el gas entre quema, propina y devolución, y qué prueba realmente un receipt con status 0 o 1.

Ruta Ethereum: 0/48

Tesorería envía un pago a un proveedor y la wallet devuelve un hash. Una hora después el proveedor dice que no recibió nada. Hay tres explicaciones posibles: la transacción nunca entró en un bloque, entró y falló, o entró, funcionó y el sistema que la vigila leyó mal el resultado. Este capítulo enseña a distinguirlas con evidencia, no con intuición.

Una transacción es una instrucción firmada por una EOA que pide cambiar el estado: mover ether, llamar a un contrato o crear uno. Firmarla expresa una intención. Solo el receipt, el registro que deja la ejecución dentro de un bloque, dice qué pasó con esa intención.

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

EOA emisora y proponente del bloque

La EOA firma y paga; el proponente decide si la incluye y en qué orden.

Evidencia

Receipt: status, gasUsed, logs

Existe solo cuando la transacción ya está en un bloque.

Estado

Nonce, balances y storage tocados

El nonce sube y el gas se cobra aunque la ejecución revierta.

Preguntas guía

  • ¿Qué campos firma el emisor y cuáles calcula la red?
  • ¿Por qué el gas tiene una cantidad (unidades) y un precio por unidad, y quién recibe cada parte del pago?
  • ¿Qué significa un receipt con status igual a 0 y por qué igual cuesta dinero?
  • ¿Cuándo un hash sin receipt indica un problema y cuándo solo indica espera?

Conexiones dentro de la serie

Anatomía de una transacción tipo 2

Al principio Ethereum tenía un solo formato de transacción: una lista RLP con nonce, precio de gas, límite de gas, destino, valor, datos y firma. RLP (Recursive Length Prefix) es la serialización binaria que usa la capa de ejecución. EIP-2718 introdujo un sobre con tipo: TransactionType || TransactionPayload, donde el tipo es un byte entre 0x00 y 0x7f. Las transacciones antiguas se reconocen porque su primer byte está entre 0xc0 y 0xfe, el rango de una lista RLP.

TipoNombreQué añadeUpgrade
0x00LegacygasPrice único; chain ID dentro de v (EIP-155)Frontier / Spurious Dragon
0x01Access listlista de direcciones y slots que se tocaránBerlin
0x02EIP-1559maxFeePerGas y maxPriorityFeePerGasLondon
0x03Blobblobs para rollups; no puede crear contratosDencun
0x04Set codeautorizaciones que delegan código a una EOAPectra

El chain ID de las transacciones legacy llegó con EIP-155 en Spurious Dragon. Los tipos 1 y 2 y sus upgrades vienen de la documentación de ethereum.org y de sus EIPs. Los tipos 3 y 4 vienen de EIP-4844 y EIP-7702, incluidos en Dencun y en Pectra. El tipo 2 es hoy el habitual. Sus campos son chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value, data, access_list y la firma (y_parity, r, s).

Falta un campo que muchos esperan: from. Como vimos en el capítulo anterior, la red recupera el emisor desde la firma. Si to está vacío, la transacción crea un contrato y data contiene el código de inicialización.

Gas: cantidad de trabajo, precio y tope

Gas es la unidad con la que la EVM mide trabajo. Separar unidades de precio permite que el costo de una operación sea estable aunque el precio del ether cambie. Toda transacción paga una base de 21 000 gas; crear un contrato suma 32 000; cada byte de data cuesta 4 gas si es cero y 16 si no lo es. Por eso una transferencia simple de ether usa exactamente 21 000 gas.

Desde Pectra, EIP-7623 añade un piso a las transacciones cuyo gas viene casi todo de los datos: el costo de data nunca baja de 10 gas por "token", donde un byte cero cuenta como 1 token y uno no cero como 4. El objetivo es acotar el tamaño máximo de bloque sin encarecer el uso normal.

El campo gas_limit es el máximo que el emisor acepta gastar. Desde Fusaka, activado en mainnet el 3 de diciembre de 2025, ninguna transacción puede declarar más de 16 777 216 gas (2242^{24}), aunque el límite del bloque sea mayor. Para un equipo que despliega contratos grandes o ejecuta lotes, este tope es una restricción de diseño, no un detalle.

Traza de cobro con EIP-1559

EIP-1559 divide el precio por unidad de gas en dos partes. La base fee la fija el protocolo para cada bloque y se quema: nadie la recibe. La priority fee o propina va a quien propone el bloque. El emisor no fija ninguna de las dos directamente; fija dos topes.

La especificación calcula el cobro así:

prio=min⁡(maxPriorityFee, maxFee−baseFee)precio=baseFee+prio\mathit{prio} = \min(\mathit{maxPriorityFee},\ \mathit{maxFee} - \mathit{baseFee}) \qquad \mathit{precio} = \mathit{baseFee} + \mathit{prio}

Antes de ejecutar, el emisor debe tener saldo para gas_limit × maxFee más el valor enviado. Se le cobra gas_limit × precio. Al terminar se le devuelve (gas_limit − gasUsed) × precio, se quema gasUsed × baseFee y el proponente recibe gasUsed × prio.

Ejemplo de la documentación oficial: una transferencia de 1 ETH con base fee de 190 gwei y propina de 10 gwei cuesta 21 000×200=4 200 00021\,000 \times 200 = 4\,200\,000 gwei, es decir 0,0042 ETH. De eso, 0,00399 ETH se queman y 0,00021 ETH van al validador. Un gwei son 10910^9 wei.

Figura 1

Ciclo de vida de una transacción y reparto del gas

Fuentes:

[9][1]
Ciclo de vida de una transacción y reparto del gas
Cómo leer esta figura

Fila superior con cinco cajas unidas por flechas: Firma en la wallet, Mempool pendiente sin receipt, Inclusión donde un bloque fija orden y base fee, Ejecución donde la EVM consume gas, y Receipt con status, gasUsed y logs. Una línea discontinua baja desde Ejecución con el rótulo aquí se mide gasUsed. Debajo, una barra horizontal en tres tramos: gasUsed por baseFee, en rojo, se quema; gasUsed por prio, en azul, va al fee recipient; gasLimit menos gasUsed por precio, en verde, vuelve al emisor. Al pie, la fórmula prio igual al mínimo entre maxPriorityFee y maxFee menos baseFee, y precio efectivo igual a baseFee más prio.

Visual original de TrautsLab. La transacción pasa de firmada a pendiente, incluida, ejecutada y registrada en un receipt. El gas efectivamente usado se divide entre la base fee que se quema y la propina; el resto del límite vuelve al emisor.

La base fee se ajusta sola. Cada bloque tiene un objetivo de gas igual a la mitad de su límite. Si el bloque padre usó más que el objetivo, la base fee sube; si usó menos, baja; el cambio máximo por bloque es un octavo, el 12,5 %. Una consecuencia operativa: maxFee debe dejar margen para varios bloques de subida si la transacción no es urgente, porque si la base fee supera maxFee la transacción no puede incluirse.

El receipt como evidencia: qué prueba y qué no

Cuando la transacción entra en un bloque, el cliente produce un receipt. Para los tipos con sobre, su contenido es rlp([status, cumulative_transaction_gas_used, logs_bloom, logs]). La interfaz JSON-RPC añade campos derivados, como gasUsed, effectiveGasPrice, contractAddress, blockNumber y type.

El campo status existe desde EIP-658. Antes, el receipt guardaba una raíz de estado intermedia y, tras la llegada del opcode REVERT, ya no se podía saber si una transacción había fallado mirando solo si consumió todo el gas. Hoy la regla es simple: 1 significa que la llamada de nivel superior terminó bien y sus cambios se aplicaron; 0 significa que falló y sus cambios de estado se descartaron.

Un status 0 no es gratis. El nonce del emisor sube y el gas consumido hasta el fallo se paga, porque los validadores hicieron el trabajo. Los logs del intento fallido no aparecen en el receipt. Por eso un indexador que solo escucha eventos nunca verá las transacciones que revirtieron.

Diagnóstico

Tres síntomas, tres causas distintas

Reglas de decisión rápida

  • No hay receipt para el hash

    eth_getTransactionReceipt devuelve null; la transacción puede estar pendiente, reemplazada o nunca haber llegado a un nodo.

    → Revisar nonce pendiente, maxFee frente a base fee y si el nodo conoce la transacción.

  • Receipt con status 0

    Incluida y revertida: gas cobrado, nonce consumido, sin logs.

    → Reproducir la llamada con eth_call en el bloque anterior para leer el motivo del revert.

  • Receipt con status 1 pero el proveedor no ve el pago

    La ejecución funcionó; el problema está en qué se leyó o en qué bloque.

    → Comparar destino, valor y logs del receipt con lo esperado, y confirmar que el bloque sigue en la cadena canónica.

Empieza siempre por el receipt: separa no incluida, incluida con fallo e incluida con éxito.

Un receipt prueba que la transacción fue incluida en un bloque concreto y qué resultado tuvo allí. No prueba que ese bloque sea definitivo. Mientras el bloque no esté finalizado, una reorganización puede sacarlo de la cadena canónica; el capítulo sobre JSON-RPC explica cómo pedir el estado safe o finalized en lugar de latest.

Riesgo operativo: nonces atascados y reemplazos

El nonce ordena las transacciones de una cuenta sin excepciones. Si la transacción con nonce 7 no se incluye, las de nonce 8 y 9 esperan aunque paguen más. Para un servicio que firma en paralelo, el nonce es un recurso compartido: necesita un único asignador y una política para huecos.

Mientras una transacción está pendiente, se puede enviar otra con el mismo nonce y comisiones más altas. Si la segunda se incluye, la primera se vuelve inválida. Los umbrales para aceptar ese reemplazo son política del cliente que mantiene el mempool, no regla de consenso, así que conviene leerlos en la documentación del cliente o proveedor que uses. Lo que sí es regla de protocolo: una firma válida tiene un solo hash, porque desde Homestead se rechazan las firmas con ss alto.

Práctica local: seguir una transferencia en Anvil

Anvil es el nodo local de Foundry. Escucha por defecto en el puerto 8545, usa el chain ID 31337 y crea cuentas de prueba con fondos a partir de una mnemónica pública. Nada de esto toca mainnet.

Bash
# Terminal 1: nodo local efímero
anvil
 
# Terminal 2: transferir 1 ether de la cuenta de prueba 0 a la cuenta 1
export ETH_RPC_URL=http://127.0.0.1:8545
PK=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
cast send 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 --value 1ether --private-key $PK
 
# Leer el receipt completo y solo el campo status (usa el hash que imprimió cast send)
cast receipt <TX_HASH>
cast receipt <TX_HASH> status

Qué observar: gasUsed debe ser 21000 porque es una transferencia sin datos, status debe ser 1 y effectiveGasPrice debe ser la suma de la base fee del bloque y la propina efectiva. Para ver un status 0 necesitas desplegar en este mismo Anvil un contrato cuya función revierta. Cuando lo hagas, compara: el receipt existirá, status será 0 y el nonce de la cuenta habrá subido igual (cast nonce <DIRECCION>). Para limpiar, basta con detener Anvil; su estado vive en memoria.

Referencias

Bibliografía académica

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

    ethereum.org, "Transactions," Ethereum developer documentation. [Online]. Available: https://ethereum.org/developers/docs/transactions/

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

    Campos de una transacción, tipos y ciclo de vida.

  2. [2]

    M. Zoltu, "EIP-2718: Typed transaction envelope," Ethereum Improvement Proposals, 2020.

    Sobre TransactionType || TransactionPayload para transacciones y receipts.

  3. [3]

    V. Buterin, "EIP-155: Simple replay attack protection," Ethereum Improvement Proposals, 2016.

    Chain ID dentro de la firma para evitar replay entre redes.

  4. [4]

    V. Buterin and M. Swende, "EIP-2930: Optional access lists," Ethereum Improvement Proposals, 2020.

    Transacción tipo 0x01 con lista de acceso.

  5. [5]

    V. Buterin et al., "EIP-4844: Shard blob transactions," Ethereum Improvement Proposals, 2022.

    Transacción tipo 0x03 con blobs y su receipt.

  6. [6]

    T. Beiko, "EIP-7569: Hardfork meta - Dencun," Ethereum Improvement Proposals, 2023.

    EIPs de Dencun y activación en mainnet.

  7. [7]

    V. Buterin, S. Wilson, A. Dietrichs and lightclient, "EIP-7702: Set code for EOAs," Ethereum Improvement Proposals, 2024.

    Transacción tipo 0x04 y autorizaciones que delegan código a una EOA.

  8. [8]

    T. Beiko, "EIP-7600: Hardfork meta - Pectra," Ethereum Improvement Proposals, 2024.

    EIPs incluidos en Pectra y su activación en mainnet.

  9. [9]

    V. Buterin et al., "EIP-1559: Fee market change for ETH 1.0 chain," Ethereum Improvement Proposals, 2019.

    Base fee quemada, priority fee y ajuste por bloque.

  10. [10]

    G. Wood, "Ethereum: A secure decentralised generalised transaction ledger," Yellow Paper, Shanghai version efc5f9a, 2025.

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

    Especificación formal de estado, transacciones, firma (apéndice F), gas y EVM.

  11. [11]

    T. Wahrstätter and V. Buterin, "EIP-7623: Increase calldata cost," Ethereum Improvement Proposals, 2024.

    Precio mínimo por token de calldata incluido en Pectra.

  12. [12]

    G. Rebuffo and T. Wahrstätter, "EIP-7825: Transaction gas limit cap," Ethereum Improvement Proposals, 2024.

    Tope de gas por transacción.

  13. [13]

    T. Beiko, A. Stokes and A. Dietrichs, "EIP-7607: Hardfork meta - Fusaka," Ethereum Improvement Proposals, 2024.

    EIPs incluidos en Fusaka y su activación en mainnet.

  14. [14]

    ethereum.org, "Gas and fees," Ethereum developer documentation. [Online]. Available: https://ethereum.org/developers/docs/gas/

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

    Base fee, priority fee y cálculo de la comisión.

  15. [15]

    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.

  16. [16]

    N. Johnson, "EIP-658: Embedding transaction status code in receipts," Ethereum Improvement Proposals, 2017.

    Campo status del receipt.

  17. [17]

    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.