Ethereum: de historia a labs

Artículo 3 de 48

6%

completado al llegar aquí

Sistemas distribuidosNota de investigación13 min de lectura

Frontier, Homestead y The DAO: la red aprendió con golpes

De Frontier a Spurious Dragon: qué cambió Homestead, cómo se drenó The DAO, qué hizo el fork del bloque 1,920,000 y por qué nació EIP-155.

Ruta Ethereum: 0/48

El 17 de junio de 2016, la Ethereum Foundation publicó una alerta: un atacante estaba drenando el ether de The DAO, un contrato de inversión colectiva, hacia un contrato hijo. El protocolo funcionaba bien; el fallo estaba en el contrato. Aun así, la respuesta terminó en un hard fork y en dos cadenas que siguen existiendo hoy. Este capítulo explica cómo se llegó ahí.

Antes hay que ver la red que recibió el golpe. El registro oficial de forks ubica cada paso con fecha y bloque, y es la fuente de las alturas que aparecen aquí.

Línea de tiempo

Los primeros dieciséis meses de la red principal

Fechas y bloques de activación según el registro oficial de ethereum.org.
  1. 30 jul. 2015 · bloque 0

    Frontier

    Génesis. Red funcional pero mínima, pensada para desarrolladores, con un límite de 5,000 de gas por bloque.

  2. 7 sep. 2015 · bloque 200,000

    Frontier thawing

    Se levanta el límite de gas por bloque; ya caben transacciones normales.

  3. 14 mar. 2016 · bloque 1,150,000

    Homestead

    EIP-2, DELEGATECALL y compatibilidad futura de red.

  4. 20 jul. 2016 · bloque 1,920,000

    DAO fork

    Cambio de estado irregular que mueve los fondos de The DAO a un contrato de reembolso.

  5. 18 oct. 2016 · bloque 2,463,000

    Tangerine Whistle

    Primera respuesta a los ataques de denegación de servicio: opcodes de E/S más caros.

  6. 22 nov. 2016 · bloque 2,675,000

    Spurious Dragon

    Protección contra replay, limpieza de cuentas vacías y límite al tamaño del código.

Brújula de lectura

Actor, evidencia y estado

En The DAO, el actor que cambió el estado no fue un usuario sino la comunidad que eligió qué software correr.

Actor

Operadores de nodos

Cada operador decidió si instalaba un cliente que aplicaba el fork o uno que lo rechazaba.

Evidencia

EIP-779

Especifica la lista de cuentas, el bloque de activación y el contrato destino del cambio.

Estado

Saldos de la lista L

Solo cambiaron saldos de cuentas concretas; opcodes y formatos quedaron iguales.

Preguntas guía

  • ¿Qué cambió Homestead en la creación de contratos y en las firmas de transacciones?
  • ¿Por qué un contrato que envía ether antes de actualizar su contabilidad puede ser vaciado?
  • ¿En qué se diferencia el DAO fork de un fork que cambia reglas del protocolo?
  • Después de un fork contencioso, ¿por qué una transacción firmada para una cadena puede ejecutarse en la otra?

Conexiones dentro de la serie

Historia de Frontier y Homestead: una red para desarrolladores

Frontier arrancó el 30 de julio de 2015. ethereum.org la describe como una implementación funcional pero mínima, pensada para usuarios técnicos. Durante las primeras semanas cada bloque tenía un límite de 5,000 de gas, menos que los 21,000 que cuesta una transferencia simple: los mineros podían arrancar, pero nadie podía mover ether todavía. El fork "Frontier thawing" del bloque 200,000 levantó ese límite.

Homestead se activó en el bloque 1,150,000, el 14 de marzo de 2016. Su especificación agrupa tres propuestas: EIP-2, EIP-7 y EIP-8.

EIP-2 es la que más afecta a un desarrollador. Crear un contrato con una transacción pasa a costar 53,000 de gas de base en lugar de 21,000. Si la creación no tiene gas suficiente para guardar el código, falla por completo en lugar de dejar un contrato vacío. Y las firmas con un valor s mayor que la mitad del orden de la curva secp256k1 pasan a ser inválidas, lo que elimina la maleabilidad: antes cualquiera podía invertir s y v y obtener otra firma válida de la misma transacción sin conocer la clave.

EIP-7 añadió el opcode DELEGATECALL: ejecutar el código de otro contrato en el contexto propio (el mismo storage), conservando el emisor y el valor de la llamada original. Es la pieza sobre la que, años después, se construyeron los contratos actualizables mediante proxy.

Traza de ejecución del ataque: llamar antes de actualizar

The DAO custodiaba ether de sus participantes y les permitía "dividirse" (split): salir hacia un contrato hijo, un DAO nuevo, con sus fondos. La alerta de la fundación describe el fallo como una vulnerabilidad de llamada recursiva en la función de división. El atacante hacía que el contrato le enviara ether y, antes de que el contrato registrara el retiro, volvía a pedir.

El patrón se entiende mejor con un contrato mínimo. Este código no es el de The DAO; es una versión reducida del mismo error:

solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
 
/// Ilustrativo y vulnerable. NO es el código de The DAO: reproduce el patrón
/// "enviar ether antes de actualizar el saldo".
contract VulnerableVault {
    mapping(address => uint256) public balances;
 
    function deposit() external payable {
        balances[msg.sender] += msg.value;
    }
 
    function withdraw() external {
        uint256 amount = balances[msg.sender];
        (bool ok, ) = msg.sender.call{value: amount}("");
        require(ok, "transfer failed");
        // Demasiado tarde: durante la llamada anterior el receptor pudo
        // invocar withdraw() otra vez y leer el mismo saldo.
        balances[msg.sender] = 0;
    }
}

La traza es esta. El atacante deposita desde un contrato propio y llama a withdraw. La bóveda envía ether, y eso ejecuta el código del atacante, que vuelve a llamar a withdraw. Como el saldo todavía no se puso a cero, la bóveda paga otra vez. El ciclo se repite hasta agotar el gas o los fondos. La corrección es mover balances[msg.sender] = 0 antes de la llamada externa.

En The DAO había un detalle que dio margen: el ether drenado quedó en un DAO hijo, y la alerta explicaba que el atacante no podría retirarlo durante al menos unos 27 días, el período de creación del hijo. Ese plazo abrió el debate sobre qué hacer.

Invariante rota: un cambio de estado irregular en el bloque 1,920,000

La invariante de la que hablaba el capítulo anterior es que todos los nodos aplican las mismas reglas a las mismas transacciones. El DAO fork no cambió ninguna regla. Según su especificación, opcodes, formato de transacciones y estructura de bloques quedaron iguales. Lo que hizo fue un cambio de estado irregular: una modificación de saldos que no proviene de ninguna transacción.

La mecánica es precisa. En el bloque 1,880,000 se fijó una lista L de cuentas: The DAO, su cuenta extraBalance y todos los DAO hijos con las suyas. Al comienzo del bloque 1,920,000, todo el ether de esas cuentas se transfirió a un contrato de reembolso, WithdrawDAO, en 0xbf4ed7b27f1d666546e30d74d50d173d20bca754. Ese contrato tiene una sola función útil: withdraw, que devuelve 1 ETH por cada 100 tokens DAO.

Figura 1

El DAO fork: una historia común y dos estados distintos

Fuentes:

[5]
El DAO fork: una historia común y dos estados distintos
Cómo leer esta figura

Línea de tiempo horizontal de bloques con una marca vertical en el bloque 1,920,000. A la izquierda, la caja historia común hasta el bloque 1,919,999. De ella salen dos flechas: una verde hacia la caja cadena con el fork, ETH, donde un cambio de estado irregular mueve el ether de las cuentas de la lista L a WithdrawDAO; y una ocre hacia la caja cadena sin el fork, ETC, con las mismas reglas y ningún saldo movido. Debajo, una nota explica que la misma clave y el mismo formato de transacción en ambas cadenas permiten que una firma válida en una también sea válida en la otra, lo que se llama replay. A la derecha, una caja azul: EIP-155, noviembre de 2016, la firma incluye CHAIN_ID.

Visual original de TrautsLab basado en EIP-779 y en el registro de forks. Hasta el bloque 1,919,999 hay una sola historia; desde el 1,920,000 conviven una cadena con el cambio de estado y otra sin él. EIP-155 llegó después para separar las firmas.

La decisión no la tomó un administrador. Los clientes publicaron versiones con el fork activado o desactivado, y cada operador eligió. ethereum.org registra que la opción se votó con transacciones en una plataforma abierta a cualquier poseedor de ETH y superó el 85 % de los votos. Algunos mineros rechazaron el fork con el argumento de que el fallo no estaba en el protocolo, y esa cadena siguió como Ethereum Classic.

El día del fork, la fundación informó que el bloque 1,920,000 movió unos 12 millones de ETH desde los contratos del atacante y de un grupo de rescate al contrato de reembolso.

Riesgo operativo: dos cadenas, una misma firma

Ese mismo anuncio advertía a quien siguiera la cadena sin fork que tuviera cuidado con los ataques de replay. Ambas cadenas compartían claves, direcciones y formato de transacción. Una transferencia firmada para una red era válida en la otra, y cualquiera podía retransmitirla.

La solución llegó con Spurious Dragon, en el bloque 2,675,000. Su especificación incluye EIP-155, EIP-160, EIP-161 y EIP-170. EIP-155 hace que la firma cubra también un identificador de cadena (CHAIN_ID, que vale 1 en la red principal). Una transacción firmada así solo es válida en la cadena para la que se firmó.

Entre el DAO fork y Spurious Dragon hubo otro incidente: ataques de denegación de servicio en septiembre y octubre de 2016 que abusaban de opcodes demasiado baratos para el trabajo que imponían a los nodos. Tangerine Whistle, en el bloque 2,463,000, subió esos precios con EIP-150: SLOAD pasó de 50 a 200 de gas, BALANCE de 20 a 400 y CALL y EXTCODESIZE a 700. También introdujo la regla de "todo menos 1/64": una llamada solo puede reenviar como máximo 63/64 del gas que queda.

La lección operativa para un equipo de hoy tiene tres partes. Un contrato con fondos ajenos es un sistema de producción: la regla de "actualizar estado antes de llamar afuera" no es estilo, es seguridad. Un hard fork es una decisión de cada operador de nodo, y tu infraestructura debe saber qué versión corre. Y cualquier sistema que firme transacciones debe fijar el chainId correcto; las librerías actuales lo hacen por defecto, pero conviene verificarlo en las pruebas.

Referencias

Bibliografía académica

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

    ethereum.org, "Timeline of all Ethereum forks (2014 to present)."

    official docsID: ETH-HISTORY-FORKSAbrir fuente (abre en otra pestaña)

    Fechas y bloques de Frontier, Homestead, DAO fork, Tangerine Whistle y Spurious Dragon.

  2. [2]

    A. Beregszaszi, "EIP-606: Hardfork Meta: Homestead," Ethereum Improvement Proposals, no. 606, Apr. 2017.

    Bloque de activación 1,150,000 y lista de EIPs incluidos en Homestead.

  3. [3]

    V. Buterin, "EIP-2: Homestead Hard-fork Changes," Ethereum Improvement Proposals, no. 2, Nov. 2015.

    Costo de creación de contratos, fallo atómico de creación y restricción del valor s en firmas.

  4. [4]

    V. Buterin, "CRITICAL UPDATE Re: DAO Vulnerability," Ethereum Foundation Blog, Jun. 17, 2016.

    foundation blogID: EF-DAO-ALERT-2016Abrir fuente (abre en otra pestaña)

    Descripción contemporánea del ataque recursivo y del plazo de 27 días del DAO hijo.

  5. [5]

    C. Detrio, "EIP-779: Hardfork Meta: DAO Fork," Ethereum Improvement Proposals, no. 779, Nov. 2017.

    Especificación del cambio de estado irregular: lista L, bloque 1,920,000 y contrato WithdrawDAO.

  6. [6]

    V. Buterin, "Hard Fork Completed," Ethereum Foundation Blog, Jul. 20, 2016.

    foundation blogID: EF-DAO-FORK-DONE-2016Abrir fuente (abre en otra pestaña)

    Confirmación del fork, ETH movido a WithdrawDAO y advertencia sobre replay en la cadena sin fork.

  7. [7]

    A. Beregszaszi, "EIP-607: Hardfork Meta: Spurious Dragon," Ethereum Improvement Proposals, no. 607, Apr. 2017.

    Bloque 2,675,000 y lista de EIPs de Spurious Dragon.

  8. [8]

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

    Inclusión de CHAIN_ID en el hash firmado de la transacción.

  9. [9]

    V. Buterin, "EIP-150: Gas cost changes for IO-heavy operations," Ethereum Improvement Proposals, no. 150, Sep. 2016.

    Nuevos costos de opcodes de E/S y regla de 63/64 del gas en llamadas.

  10. [10]

    V. Buterin, "EIP-7: DELEGATECALL," Ethereum Improvement Proposals, no. 7, Nov. 2015.

    Opcode 0xf4: como CALLCODE pero propagando emisor y valor de la llamada padre.

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.