Ethereum: de historia a labs

Artículo 4 de 48

8%

completado al llegar aquí

Sistemas distribuidosNota de investigación14 min de lectura

Metropolis a Istanbul: Ethereum madura antes del Merge

Byzantium, Constantinople, Petersburg, Istanbul y Muir Glacier: qué EIPs activó cada fork, por qué se pospuso Constantinople y qué cambió para contratos.

Ruta Ethereum: 0/48

El 15 de enero de 2019, a unas 32 horas de un hard fork programado, los desarrolladores de Ethereum decidieron posponerlo. Un cambio en el precio de SSTORE (escribir en storage) podía volver vulnerables a contratos que ya estaban desplegados y que nunca habían cambiado una línea. Ese episodio resume este capítulo: un fork cambia las reglas debajo de código que no se puede editar.

Entre 2017 y 2020, Ethereum activó cinco forks antes de empezar la transición a proof-of-stake. El registro oficial lista cada uno con su fecha, bloque y EIPs.

Figura 1

De Byzantium a Muir Glacier por altura de bloque

Fuentes:

[1][9]
De Byzantium a Muir Glacier por altura de bloque
Cómo leer esta figura

Línea de tiempo horizontal por número de bloque en la red principal. En el bloque 4,370,000, Byzantium, octubre de 2017: REVERT, STATICCALL y status en receipts. En el bloque 7,080,000, marcado en ocre, una caja dice no se activó: en enero de 2019 EIP-1283 abría reentrada y el fork se pospuso. En el bloque 7,280,000, Constantinople más Petersburg, febrero de 2019: CREATE2, desplazamientos de bits y EXTCODEHASH, sin EIP-1283. En el bloque 9,069,000, Istanbul, diciembre de 2019: CHAINID y calldata a 16 de gas por byte. En el bloque 9,200,000, Muir Glacier, enero de 2020: solo retrasa la bomba de dificultad.

Visual original de TrautsLab basado en los meta-EIPs 609, 1013, 1716, 1679 y 2384. Constantinople estaba previsto para el bloque 7,080,000; se activó en el 7,280,000 junto con Petersburg, que retiró EIP-1283.

Brújula de lectura

Actor, evidencia y estado

Un fork cambia el costo y la semántica de opcodes para todos los contratos a la vez.

Actor

Clientes y operadores

Cada cliente implementa los EIPs del fork; cada operador actualiza antes del bloque de activación.

Evidencia

Meta-EIP del fork

Un documento por fork con su nombre, bloque de activación y lista exacta de EIPs.

Estado

Reglas de la EVM

Opcodes nuevos, costos de gas y formato de receipts que cambian desde un bloque concreto.

Preguntas guía

  • ¿Qué podía hacer un contrato después de Byzantium que antes no podía?
  • ¿Cómo se calcula de antemano la dirección de un contrato creado con CREATE2?
  • ¿Por qué abaratar SSTORE podía abrir una reentrada en contratos ya desplegados?
  • ¿Qué hizo Istanbul para que fuera más barato publicar datos en la cadena?

Conexiones dentro de la serie

Historia de Metropolis: un upgrade en dos entregas

"Metropolis" fue el nombre de una etapa que se entregó en dos forks. Byzantium es "Metropolis parte 1" y Constantinople, "Metropolis parte 2", según sus propias especificaciones.

Byzantium se activó en el bloque 4,370,000, el 16 de octubre de 2017, con nueve EIPs. Además de los cambios de EVM que veremos en la siguiente sección, añadió precompilados (contratos nativos en direcciones fijas) para operar sobre la curva elíptica alt_bn128 y para exponenciación modular, la base criptográfica para verificar pruebas de conocimiento cero dentro de un contrato.

Byzantium también retrasó la bomba de dificultad y bajó la recompensa por bloque de 5 a 3 ETH. La bomba es un término de la fórmula de dificultad de proof-of-work que crece de forma exponencial cada 100,000 bloques. Su propósito era volver la minería cada vez más lenta para forzar la migración a proof-of-stake. El retraso vino acompañado de una recompensa menor para no aumentar la emisión; Constantinople repitió la fórmula y bajó la recompensa a 2 ETH.

Muir Glacier, en el bloque 9,200,000 (enero de 2020), hizo una sola cosa: retrasar de nuevo la bomba 4,000,000 de bloques. Su motivación registra que los tiempos de bloque habían pasado de unos 13,1 a 14,3 segundos y que seguirían empeorando.

Traza de ejecución: qué ve un contrato después de Byzantium

Antes de Byzantium, un contrato que quería abortar tenía dos caminos: quedarse sin gas o ejecutar una instrucción inválida. Ambos consumían todo el gas restante y no dejaban explicar el motivo. REVERT (EIP-140) detiene la ejecución, deshace los cambios de estado, devuelve un bloque de memoria con el motivo y no consume el gas que sobra. Cuando un require falla en Solidity, el compilador ejecuta este mismo opcode (0xfd).

El otro cambio que un backend nota a diario está en los receipts. Antes, el receipt guardaba una raíz de estado intermedia y no decía si la transacción había tenido éxito. EIP-658 la reemplazó por un campo status: 1 si la llamada de nivel superior tuvo éxito, 0 si falló. Cuando tu código lee receipt.status, usa este EIP.

STATICCALL (EIP-214) permite llamar a otro contrato prohibiendo cualquier modificación de estado durante esa llamada y sus subllamadas. Con un objetivo de compilación Byzantium o posterior, Solidity usa este opcode para llamar funciones view de otros contratos.

Constantinople, en el bloque 7,280,000 (28 de febrero de 2019), sumó desplazamientos de bits (SHL, SHR, SAR), EXTCODEHASH para leer el hash del código de otra cuenta y CREATE2.

CREATE2 (EIP-1014) despliega un contrato en una dirección que depende del creador, de una sal elegida y del código de inicialización, no del nonce del creador. La dirección es keccak256(0xff ++ creador ++ sal ++ keccak256(init_code)), de la que se toman los últimos 20 bytes. Eso permite conocer y usar una dirección antes de desplegar nada en ella.

solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
 
/// Ilustrativo: calcula la dirección que obtendrá un contrato desplegado
/// con CREATE2 según la fórmula de EIP-1014.
library Create2Address {
    function predict(address deployer, bytes32 salt, bytes memory initCode)
        internal
        pure
        returns (address)
    {
        bytes32 h = keccak256(
            abi.encodePacked(bytes1(0xff), deployer, salt, keccak256(initCode))
        );
        return address(uint160(uint256(h))); // últimos 20 bytes
    }
}

Invariante en peligro: EIP-1283 y el estipendio de 2300 de gas

Constantinople iba a incluir también EIP-1283, que cobraba SSTORE según el cambio neto del slot dentro de la transacción. Escribir dos veces el mismo slot pasaba a ser mucho más barato. La idea era razonable; el problema estaba en una suposición que muchos contratos hacían sin saberlo.

Cuando un contrato envía ether con transfer() o send() de Solidity, el receptor recibe solo un estipendio de 2300 de gas. Con los precios anteriores, eso no alcanzaba para escribir en storage, así que muchos desarrolladores lo trataban como una barrera contra la reentrada. Con EIP-1283, ciertas escrituras cabían en 2300 de gas y la barrera desaparecía.

La alerta de la fundación lo resume así: con EIP-1283 algunos contratos ya desplegados podían volverse vulnerables a reentrada, en particular los que usaban transfer() o send() seguido de una operación que cambia estado. ChainSecurity reportó el problema y los clientes publicaron versiones que no activaban el fork en el bloque 7,080,000.

La salida fue un segundo meta-EIP, Petersburg, que se activa en el mismo bloque 7,280,000 y retira EIP-1283. La especificación establece que, si ambos forks caen en el mismo bloque, Petersburg tiene precedencia.

Istanbul volvió a introducir la medición neta de SSTORE con EIP-2200, pero añadió una guarda: si el gas restante es menor o igual que el estipendio de 2300, SSTORE falla. Así se conservó la suposición de los contratos antiguos.

Cuello de botella: Istanbul reprecia el estado y abarata los datos

Istanbul se activó en el bloque 9,069,000, el 8 de diciembre de 2019, con seis EIPs. Dos de ellos atacan el mismo cuello de botella desde lados opuestos.

El estado de Ethereum había crecido, y leerlo era más caro para los nodos de lo que el gas reflejaba. EIP-1884 subió SLOAD de 200 a 800 de gas y BALANCE y EXTCODEHASH de 400 a 700. El propio EIP advierte que algunos contratos cuyas funciones por defecto leen storage podían dejar de caber en los 2300 de gas del estipendio.

En sentido contrario, EIP-2028 bajó el costo de cada byte no nulo de calldata de 68 a 16 de gas. Publicar datos se volvió unas cuatro veces más barato, lo que favorece a sistemas de capa 2 que suben datos comprimidos a la cadena.

Istanbul añadió además CHAINID (EIP-1344), que deja a un contrato leer el identificador de cadena. Es el complemento en la EVM de la protección contra replay del capítulo anterior: un contrato que verifica firmas puede incluir el chainId en el mensaje firmado.

Riesgo operativo

Preparar un contrato desplegado para un hard fork

Un procedimiento general, no un estándar: los contratos no se recompilan con el fork, así que la revisión se hace sobre el bytecode que ya está en la cadena.
  1. Leer el meta-EIPLista exacta de EIPs y bloque de activación.
  2. Mapear opcodes afectadosBuscar en el bytecode los opcodes cuyo costo o semántica cambia.
  3. Revisar supuestos de gasLlamadas con 2300 de gas, límites fijos y estimaciones en el backend.
  4. Probar contra un fork localEjecutar las pruebas con las reglas nuevas activadas.
  5. Actualizar nodos a tiempoCliente compatible antes del bloque de activación.

La lección para un equipo actual es que el código inmutable no vive en un entorno inmutable. Cualquier contrato que fije una cantidad de gas, o que confíe en que una operación "no cabe" en cierto presupuesto, depende de una tabla de precios que el protocolo puede cambiar.

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, bloques y EIPs de Byzantium, Constantinople, Istanbul y Muir Glacier.

  2. [2]

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

    Bloque 4,370,000 y los nueve EIPs de Byzantium.

  3. [3]

    A. Schoedon and V. Buterin, "EIP-649: Metropolis Difficulty Bomb Delay and Block Reward Reduction," Ethereum Improvement Proposals, no. 649, Jun. 2017.

    Retraso de la bomba de dificultad y recompensa base de 3 ETH.

  4. [4]

    E. Conner, "EIP-2384: Muir Glacier Difficulty Bomb Delay," Ethereum Improvement Proposals, no. 2384, Nov. 2019.

    Retraso de 4,000,000 de bloques y efecto observado de la bomba en los tiempos de bloque.

  5. [5]

    A. Beregszaszi and N. Mushegian, "EIP-140: REVERT instruction," Ethereum Improvement Proposals, no. 140, Feb. 2017.

    REVERT deshace cambios, devuelve datos y no consume el gas restante.

  6. [6]

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

    Campo status en receipts: 1 éxito, 0 fallo.

  7. [7]

    N. Savers, "EIP-1013: Hardfork Meta: Constantinople," Ethereum Improvement Proposals, no. 1013, Apr. 2018.

    Bloque 7,280,000 y EIPs incluidos en Constantinople, entre ellos EIP-1283.

  8. [8]

    V. Buterin, "EIP-1014: Skinny CREATE2," Ethereum Improvement Proposals, no. 1014, Apr. 2018.

    Opcode CREATE2 y fórmula de la dirección resultante.

  9. [9]

    A. Schoedon and M. van der Wijden, "EIP-1716: Hardfork Meta: Petersburg," Ethereum Improvement Proposals, no. 1716, Jan. 2019.

    Retira EIP-1283 de Constantinople por un vector de reentrada; mismo bloque 7,280,000.

  10. [10]

    H. Jameson, "Security Alert: Ethereum Constantinople Postponement," Ethereum Foundation Blog, Jan. 15, 2019.

    foundation blogID: EF-CONSTANTINOPLE-POSTPONE-2019Abrir fuente (abre en otra pestaña)

    Posposición del fork previsto en el bloque 7,080,000 por la reentrada habilitada por EIP-1283.

  11. [11]

    W. Tang, "EIP-2200: Structured Definitions for Net Gas Metering," Ethereum Improvement Proposals, no. 2200, Jul. 2019.

    Medición neta de SSTORE con la guarda del estipendio de 2300 de gas.

  12. [12]

    A. Beregszaszi and A. Schoedon, "EIP-1679: Hardfork Meta: Istanbul," Ethereum Improvement Proposals, no. 1679, Jan. 2019.

    Bloque 9,069,000 y los seis EIPs de Istanbul.

  13. [13]

    M. H. Swende, "EIP-1884: Repricing for trie-size-dependent opcodes," Ethereum Improvement Proposals, no. 1884, Mar. 2019.

    SLOAD 200 a 800, BALANCE y EXTCODEHASH 400 a 700; riesgo para funciones por defecto con 2300 de gas.

  14. [14]

    A. Akhunov, E. Ben-Sasson, T. Brand, L. Guthmann, and A. Levy, "EIP-2028: Transaction data gas cost reduction," Ethereum Improvement Proposals, no. 2028, May 2019.

    Calldata no nula de 68 a 16 de gas por byte.

  15. [15]

    R. Meissner and B. Eisenbach, "EIP-1344: ChainID opcode," Ethereum Improvement Proposals, no. 1344, Aug. 2018.

    Opcode CHAINID en 0x46.

  16. [16]

    Solidity Team, "Contracts," Solidity documentation.

    official docsID: SOL-CONTRACTSAbrir fuente (abre en otra pestaña)

    Las funciones view se llaman con STATICCALL desde el objetivo Byzantium.

  17. [17]

    Solidity Team, "Expressions and Control Structures," Solidity documentation.

    official docsID: SOL-CONTROL-STRUCTURESAbrir fuente (abre en otra pestaña)

    require y revert ejecutan la instrucción REVERT (0xfd).

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.