---
id: 25
title: "Estado global y Merkle Patricia Trie: cómo stateRoot lo compromete todo"
slug: 0-8-world-state-mpt
date: 2026/09/27
summary: "El stateRoot de la cabecera de un bloque ocupa solo 32 bytes, pero ata los saldos, los nonces, el código y el almacenamiento de contratos de todas las cuentas de la red. Este artículo desmonta la estructura de nodos del Merkle Patricia Trie y el trie de almacenamiento colgado de cada cuenta, explica cómo las pruebas de Merkle sostienen la verificación fuera de cadena y sobre quién recae el costo del crecimiento del estado."
keywords: Merkle Patricia Trie,stateRoot,estado global,crecimiento del estado,pruebas de Merkle
heroImage: /images/articles/photos/0-8-world-state-mpt.jpg
---

En la cabecera de un bloque de Ethereum hay un campo de 32 bytes: stateRoot. No contiene ningún dato de cuenta, pero asegura fijar en un solo valor los saldos, los nonces, el código y el almacenamiento de contratos de todas las cuentas de la red: si este hash coincide, se sabe de qué versión es ese estado. El campo es el hash raíz del árbol de Merkle Patricia (MPT) y el único compromiso que la capa de ejecución ofrece sobre el estado global.

Para entender cuánto pesa ese compromiso hay que desmontar tres cosas: la estructura del árbol, por qué el hash raíz puede atar la totalidad del estado y qué cuesta mantener ese árbol. La tercera es el punto de partida de las discusiones posteriores sobre sharding de estado y renovación del árbol de estado.

## En una cabecera de bloque conviven tres tries

La cabecera de un bloque de Ethereum lleva tres raíces de trie que describen el resultado de la ejecución: stateRoot (trie del estado global), transactionsRoot (trie de transacciones) y receiptsRoot (trie de recibos). El Yellow Paper las denota H_r, H_t y H_e. Después de la actualización Shanghai, la cabecera incorporó además withdrawalsRoot (H_w), que describe la lista de retiros; su estructura es parecida a la del trie de transacciones, salvo que cada retiro lleva pocos datos y hay pocos por bloque. El Yellow Paper incluye entre las condiciones de validez de la cabecera que «H_r sea igual a la raíz de estado que resulta de ejecutar en orden todas las transacciones del bloque y, después, todos los retiros». stateRoot describe el estado acumulado; las otras raíces describen solo este bloque.

| trie | Clave | Valor | Ciclo de vida |
|------|-------|-------|---------------|
| Trie del estado global | keccak256(dirección de la cuenta) | Cuaterna de cuenta codificada en RLP | Se actualiza de forma continua entre bloques |
| Trie de transacciones | rlp(índice de la transacción en el bloque) | rlp(transacción); en las transacciones tipadas, el prefijo de tipo concatenado con la transacción codificada | Uno por bloque; después ya no se modifica |
| Trie de recibos | rlp(índice de la transacción en el bloque) | Recibo tipado, o rlp([status, cumulativeGasUsed, logsBloom, logs]) | Uno por bloque; después ya no se modifica |

El número de hojas del trie de transacciones y del trie de recibos es proporcional al número de transacciones del bloque, así que verificar si una transacción concreta fue incluida no depende de la longitud de la cadena, solo del tamaño del bloque. El trie del estado global es único y su tamaño crece con el número de cuentas y de slots de almacenamiento de toda la red. El logsBloom de la cabecera se agrega a partir de las direcciones y los topics de los logs de los recibos y sirve para filtrar consultas de forma probabilística: es un acelerador de índices, no un compromiso.

## La dirección entra al trie ya hasheada y la cuenta es una cuaterna

En el trie del estado global la clave es keccak256(dirección de la cuenta) y el valor es la cuaterna de cuenta codificada en RLP, [nonce, balance, storageRoot, codeHash]. La dirección como tal no entra al trie: lo que entra es su hash de 256 bits; 32 bytes equivalen a 64 nibbles, es decir, como máximo 64 niveles desde la raíz hasta una hoja.

Los cuatro campos tienen una semántica precisa. nonce es el número de transacciones que ha enviado la cuenta; en una cuenta de contrato incluye además los contratos que ha creado. balance es el saldo medido en wei. codeHash es el keccak256 del código de la cuenta; el código en sí se guarda en la base de datos de estado bajo ese hash, de modo que los contratos con el mismo bytecode comparten los mismos datos. storageRoot es la raíz de otro trie, uno que pertenece solo a esa cuenta.

La estructura se presenta así como un árbol dentro de otro árbol: el nodo hoja del trie del estado global guarda el storageRoot de una cuenta, y esa raíz apunta al trie de almacenamiento propio de la cuenta. Las claves del trie de almacenamiento son keccak256(número de slot de 32 bytes) y los valores son la codificación RLP del contenido del slot (256 bits). Un valor de slot igual a cero equivale, según la especificación, a que el slot no existe; escribir un slot de vuelta a 0 tiene como efecto, en la capa de estado, borrarlo.

Hashear primero la clave hace que la forma del árbol la determine la distribución del hash, de modo que un atacante no puede moldearla eligiendo claves. Si las claves fueran directamente direcciones o números de slot, un atacante podría escoger un conjunto de claves que compartieran un prefijo largo y comprimir un subárbol entero en una cadena larguísima, inflando el costo de acceso y de prueba; keccak256 reparte las claves de forma uniforme y ese tipo de construcción deja de ser viable. El borrador de EIP-8297, al argumentar a favor de una nueva estructura de árbol, también incluye entre sus razones de diseño que la posición la determine el hash y que el árbol se mantenga equilibrado. El precio es que el número de slot es irreversible: un contrato no puede deducir del trie de almacenamiento qué slots ha escrito, y desde fuera solo se pueden consultar uno a uno los números de slot conocidos.

Conviene retener dos constantes de valor vacío, porque las pruebas y las verificaciones posteriores las usarán: la raíz del trie vacío y el hash del código de cuenta vacío.

```python
# pip install eth-utils rlp
import rlp
from eth_utils import keccak

# Raíz del trie vacío: keccak de la cadena de bytes vacía codificada en RLP
print(keccak(rlp.encode(b"")).hex())
# 56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421

# Hash del código de cuenta vacío
print(keccak(b"").hex())
# c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
```

## Tres tipos de nodo y una bandera de dos bits: cómo se acorta una ruta de 64 niveles

El MPT es un árbol de 16 ramas (hexary), no un árbol binario. El apéndice D del Yellow Paper define tres tipos de nodo y un nodo vacío. Un nodo branch tiene 17 elementos: los 16 primeros corresponden a los 16 valores posibles del siguiente nibble y el decimoséptimo queda para el caso en que la clave termina ahí. Un nodo extension es un par [encodedPath, key] que se salta un prefijo común de al menos dos nibbles. Un nodo leaf es un par [encodedPath, value] que carga la parte restante de la clave y el valor final. El nodo vacío se representa con la cadena de bytes vacía. La especificación incluye además una invariante fácil de pasar por alto: no puede existir un nodo branch con un solo elemento distinto de cero. Por eso un mismo conjunto de pares clave-valor tiene una única codificación y el hash raíz tiene un valor determinado.

La ruta se organiza por nibbles y no por bytes, así que hace falta una codificación compacta que meta en el primer nibble dos cosas: la paridad de la longitud de la ruta y el tipo de nodo.

| Primer nibble | Binario | Tipo de nodo | Longitud de la ruta |
|---------------|---------|--------------|---------------------|
| 0 | 0000 | extension | Par |
| 1 | 0001 | extension | Impar |
| 2 | 0010 | leaf | Par |
| 3 | 0011 | leaf | Impar |

Cuando la longitud es par se añade un nibble 0 detrás del primer nibble, para que el número total de nibbles de la codificación sea par y pueda volcarse a una cadena de bytes. La implementación de referencia que da ethereum.org es la siguiente (aquí el valor devuelto se presenta como bytes):

```python
def compact_encode(hexarray):
    # La ruta de un nodo leaf termina en 16; si se detecta, se quita y se pone la bandera leaf
    term = 1 if hexarray[-1] == 16 else 0
    if term:
        hexarray = hexarray[:-1]
    oddlen = len(hexarray) % 2
    flags = 2 * term + oddlen                 # El primer nibble codifica a la vez el tipo y la paridad
    if oddlen:
        hexarray = [flags] + hexarray
    else:
        hexarray = [flags] + [0] + hexarray   # Longitud par: se añade un nibble 0
    return bytes(16 * hexarray[i] + hexarray[i + 1] for i in range(0, len(hexarray), 2))
```

La compresión de rutas explica por qué la profundidad teórica de 64 nibbles está muy lejos de agotarse en la mainnet real: el borrador de EIP-8297 estima, en su sección de motivación, que la profundidad máxima actual del trie de cuentas ronda los 12 niveles. Es una estimación del propio borrador, no un conjunto de datos medidos en la mainnet; cuanto menos niveles, más corta es la prueba.

Cómo se referencian los nodos entre sí lo decide la regla de los 32 bytes. Si un nodo hijo, codificado en RLP, ocupa menos de 32 bytes, su contenido se inserta directamente en el nodo padre; si alcanza o supera los 32 bytes, el padre solo escribe keccak(RLP(nodo hijo)) como referencia. Esta regla ahorra un buen número de lecturas puntuales de nodos pequeños, a cambio de que al verificar una prueba haya que reensamblar los nodos según su forma de codificación real, sin poder suponer que cada nivel es una consulta independiente a la base de datos.

## stateRoot es un compromiso sobre la totalidad del estado

La operación de plegar toda la estructura en un solo hash ocupa una línea: tomar keccak256 de la codificación RLP del nodo raíz. La razón que da el Yellow Paper al describir el estado global es directa: el nodo raíz depende criptográficamente de todos sus datos internos, así que ese hash puede servir de identificador seguro del estado de todo el sistema.

La fuerza del compromiso viene de cómo se referencia. Un nodo padre referencia a un hijo con el hash del hijo, de modo que cualquier cambio de un solo bit en cualquier hoja altera el nodo que la contiene y, nivel a nivel, llega hasta la raíz. Sobre la resistencia a colisiones de keccak256, encontrar dos estados distintos que den la misma raíz equivale a encontrar una colisión de hash.

De ahí salen tres propiedades. El hash raíz determina de forma única un conjunto de pares clave-valor. Un estado antiguo se puede recuperar por su raíz, porque los nodos están direccionados por contenido y su estructura es inmutable: mientras el nodo siga en la base de datos, conocer la raíz antigua basta para reconstruir el estado de entonces. Y un verificador puede reconstruir el compromiso a lo largo de la ruta, ya que los hashes hermanos del camino le permiten recalcular desde una hoja hasta la raíz; el apéndice D del Yellow Paper registra el requisito de espacio de la prueba como O(log N).

La definición de la cabecera fija además el instante: stateRoot es la raíz de estado «después de ejecutar todas las transacciones y retiros del bloque y de aplicar el procesamiento final». Compromete el conjunto de pares clave-valor de ese momento, no cómo llegó el estado a ser así: historias distintas pueden converger en el mismo estado, y una misma raíz puede repetirse en varios bloques consecutivos. Tampoco compromete la disponibilidad del estado. El hash raíz no contiene dato de nodo alguno, así que para verificar una cuenta hay que obtener aparte los nodos de la ruta.

## De la raíz a una cuenta: cómo se usa una prueba de Merkle

La prueba de una cuenta es una lista de nodos que arranca en el nodo raíz y baja nivel a nivel siguiendo los nibbles de keccak256(dirección): cada nivel o bien acierta el elemento correspondiente de un branch, o bien acierta el prefijo de ruta de un extension o un leaf. La labor del verificador es mecánica: recalcula keccak(RLP(nodo)) sobre el primer nodo y comprueba que coincide con el stateRoot conocido; tras decodificarlo busca por nibble la siguiente referencia y repite hasta la hoja; el valor de la hoja es la cuenta codificada en RLP. También se pueden probar cuentas inexistentes, y la clave está en entregar el último nodo que coincide en la ruta: si es un branch, la rama correspondiente está vacía; si es un leaf, diverge de la ruta objetivo en algún nibble. La prueba de almacenamiento requiere una segunda pasada, cuyo punto de partida es el storageRoot de la cuenta en lugar del stateRoot.

EIP-1186 estandariza este proceso en eth_getProof; este es un ejemplo de petición (la dirección y el número de slot se pueden sustituir):

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_getProof",
  "params": [
    "0x7f0d15c7faae65896648c8273b6d7e43f58fa842",
    ["0x0000000000000000000000000000000000000000000000000000000000000000"],
    "latest"
  ]
}
```

En el valor devuelto, accountProof es el array de nodos que parte del stateRoot, y cada registro de storageProof parte del storageRoot de esa cuenta. Los dos son solo datos: si son fiables o no lo decide el propio verificador al recalcularlos.

Los clientes ligeros del estilo de Helios funcionan con esta idea: el cliente ligero de la capa de consenso valida primero la cabecera del bloque en la cadena de balizas, obtiene el payload de la capa de ejecución autenticado y el stateRoot, pide después eth_getProof a cualquier RPC en el que no confía y, por último, completa la verificación del MPT en local. La sección de motivación de EIP-1186 señala también que este tipo de pruebas permite a dispositivos de internet de las cosas y aplicaciones móviles verificar, con un solo hash de bloque confiable, datos de cuentas y de almacenamiento procedentes de fuentes no confiables.

## Los límites del compromiso: pruebas, raíces antiguas y clientes ligeros

La capacidad de una prueba tiene un techo claro. Solo cubre la ruta probada y no permite deducir el resto del estado; su tamaño crece con la profundidad de la ruta y la anchura de las ramas. El borrador de EIP-8297 ilustra este orden de magnitud con un cálculo de ejemplo: partiendo de una profundidad máxima del trie de cuentas de unos 12 niveles, una rama de cuenta necesita 12 niveles, con 15 hashes hermanos por nivel, lo que suma 15 × 32 × 12 = 5760 bytes; si los 60 M de gas se dedicaran por completo a tocar un solo byte de un gran número de códigos de contrato distintos, y el código no se dividiera en bloques, el borrador estima un volumen de prueba de unos 1,8 GB. Todas son estimaciones del propio borrador, sin revisión sobre mediciones de la mainnet: la conclusión de dirección es aceptable, pero las cifras concretas no deberían tomarse como referencia. Es también la razón de que el MPT se considere poco amigable para las pruebas de validez: la codificación RLP, el hash Keccak, la estructura de árbol dentro de otro árbol y el hecho de que el código no se pueda probar por segmentos.

Las pruebas de raíces antiguas no están disponibles para siempre. El compromiso permanece en la cabecera; que se pueda cumplir depende de cómo el cliente guarde el estado. El archivo basado en rutas de Geth desde la v1.16 conserva diferencias históricas del estado plano y permite leer estados antiguos, pero en la v1.16.x no puede generar pruebas de Merkle para raíces antiguas; a partir de la v1.17 sí admite pruebas históricas conservando el historial del trie de forma explícita. El archivo basado en hashes tradicional conserva los nodos históricos del trie y puede emitir pruebas para cualquier raíz antigua. El costo en disco se ve en la sección siguiente. La semántica del compromiso pertenece al consenso; la forma de cumplirlo, a las decisiones de implementación.

Los clientes ligeros de la capa de ejecución también vivieron un repliegue. El protocolo LES de la mainnet dejó de funcionar de forma fiable durante mucho tiempo después de la Fusión y Geth eliminó el código correspondiente a finales de 2023. Hoy la verificación fuera de cadena aparece más bien como la combinación «la capa de consenso autentica la raíz y la capa de ejecución aporta la prueba», y el cliente ligero de consenso valida cabeceras de la cadena de balizas, sin reejecutar transacciones por su cuenta.

## Crecimiento del estado: la factura del compromiso cae en el disco

La capacidad expresiva de stateRoot no depende del tamaño del estado; el costo de mantenerlo, en cambio, es proporcional a ese tamaño. Cada actualización de estado obliga a reescribir la ruta completa desde la hoja hasta la raíz: cuanto más profundo y más ancho es el estado, más lecturas y escrituras necesita cada transacción. Un nodo nuevo que quiera alcanzar la cadena también tiene que descargar ese estado por completo.

Un análisis publicado en el foro de investigación de Ethereum en noviembre de 2025 da un orden de magnitud. Según el texto, en mayo de 2025 un nodo Geth que solo albergaba estado ocupaba unos 340 GiB de base de datos sin comprimir; tras subir el límite de gas de 30 M a 36 M, la mediana del estado nuevo diario pasó de unos 102 MiB a unos 205 MiB. La página del proyecto bloatnet marca 650 GB como escala crítica y afirma que, cerca de ese tamaño, el tiempo de acceso al estado aumenta alrededor de un 40 % y el consumo de memoria y el tiempo de sincronización empeoran de forma notable; la página no ofrece datos de referencia reproducibles, aquí se recoge su propio criterio y además usa GB, no GiB. El análisis del foro extrapola después tres trayectorias de límite de gas —conservadora, de referencia y agresiva— (que a mediados de 2027 llegarían a 200 M, 400 M y 700 M) y concluye que el tamaño total del estado a mediados de 2027 se situaría entre 686 GiB y 1,08 TiB. Es una extrapolación de escenarios, no un hecho consumado: los valores dependen de la implementación del cliente, de las estrategias de compresión y poda y de la evolución real del límite de gas.

Bajando a la operación de nodos, ethereum.org cifra en más de 500 GB el disco que necesita un nodo completo de Geth (sincronización snap); en el caso de los nodos de archivo, el criterio oficial de Geth es de unos 2 TB en modo basado en rutas, unos 6,5 TB conservando datos históricos del trie y más de 20 TB en modo basado en hashes, mientras que los requisitos de archivo entre clientes que recoge ethereum.org van de 3 TB a más de 12 TB. Estas cifras dependen de la implementación del cliente, del método de compresión, de la estrategia de poda y del límite de gas, así que compararlas directamente entre implementaciones no tiene sentido; el tamaño del estado tampoco equivale al tamaño del historial en cadena: las transacciones y los recibos históricos son otra cuenta.

El estado de la mainnet de Ethereum solo crece: una vez que se escribe una cuenta o un slot de almacenamiento, ocupa espacio para siempre, y no existe ningún mecanismo de expiración ni de renta de estado, de modo que la presión se acumula en una sola dirección. Las vías de mitigación son más o menos dos: cambiar la estructura del árbol, por ejemplo el árbol de Verkle del que se habla desde hace tiempo y el árbol binario particionado (Partitioned Binary Tree, borrador de EIP-8297), que sigue en fase de borrador; o bien cortar el estado en partes para que cada nodo mantenga solo una porción. Esto último es el tema de artículos posteriores; aquí basta con señalar de dónde viene la presión. Cómo se disponen los slots en la capa de contrato se trata en 0.19 «Storage Layout».

## Fuentes

- [Ethereum Yellow Paper](https://ethereum.github.io/yellowpaper/paper.pdf): la función de transición de estado de la sección 2, los campos de la cabecera y la validez global del capítulo 4 (definición y condiciones de verificación de stateRoot), la definición de los nodos del MPT del apéndice D, la codificación hex-prefix, la regla de inserción en línea de 32 bytes y el espacio de prueba O(log N) de D.1.
- [ethereum.org: Merkle Patricia Trie](https://ethereum.org/developers/docs/data-structures-and-encoding/patricia-merkle-trie/): los tipos de nodo, la implementación de referencia de la codificación compacta, la definición de claves y valores de los tres tries y la codificación de los valores de transacciones y recibos.
- [ethereum.org: Ethereum accounts](https://ethereum.org/developers/docs/accounts/): la formulación oficial de storageRoot y codeHash.
- [EIP-1186: RPC-Method to get Merkle Proofs](https://eips.ethereum.org/EIPS/eip-1186): los campos de eth_getProof, la forma de probar la inexistencia y sus casos de uso; el storageHash vacío (0x56e81f…) y el codeHash vacío (0xc5d246…) del ejemplo oficial sirven además para contrastar las dos constantes de valor vacío del bloque de código del texto.
- [EIP-8297: Partitioned Binary Tree](https://eips.ethereum.org/EIPS/eip-8297) (Draft, creado el 2026-06-11, función de hash aún sin cerrar): la profundidad máxima del trie de cuentas, unos 12 niveles; la prueba de rama de 5760 bytes; el volumen de prueba de unos 1,8 GB en el peor caso, y las razones por las que el MPT no es amigable para las pruebas de validez. En el texto, todos estos puntos se marcan como estimaciones del propio borrador.
- [Ethereum Research: State growth scenarios and the impact of repricings](https://ethresear.ch/t/state-growth-scenarios-and-the-impact-of-repricings/23476) (2025-11-19): el tamaño de estado de 340 GiB, el crecimiento diario de 102 MiB a 205 MiB y la extrapolación de escenarios para 2027 según tres trayectorias de límite de gas. Es un texto de análisis de escenarios, no un hecho consumado.
- [Bloatnet Initiative](https://cperezz.github.io/bloatnet-website/): la escala crítica de 650 GB y el aumento de alrededor del 40 % en el tiempo de acceso al estado según el propio criterio del proyecto; la página no incluye datos de referencia reproducibles.
- [go-ethereum: Archive mode](https://geth.ethereum.org/docs/fundamentals/archive): las cifras de 2 TB y 6,5 TB del archivo basado en rutas, el archivo basado en hashes que puede superar los 20 TB y las diferencias entre la v1.16.x y la v1.17 en el soporte de pruebas históricas.
- [ethereum.org: Ethereum archive node](https://ethereum.org/developers/docs/nodes-and-clients/archive-nodes/) y [Spin up your own Ethereum node](https://ethereum.org/developers/docs/nodes-and-clients/run-a-node/): los intervalos de requisitos de disco de archivo y de nodo completo entre clientes (archivo, de 3 TB a más de 12 TB; Geth con sincronización snap, más de 500 GB).
- [go-ethereum PR #28586](https://github.com/ethereum/go-ethereum/pull/28586): la eliminación de LES y del código de cliente ligero asociado.
- [a16z crypto: Building Helios](https://a16zcrypto.com/posts/article/building-helios-ethereum-light-client/): la ruta de cliente ligero en la que la capa de consenso autentica el stateRoot y la capa de ejecución verifica en local con eth_getProof.

## Lecturas relacionadas

- Relacionado: [La tensión entre descentralización y rendimiento: requisitos de validadores, hardware y distribución geográfica](/es/blog/decentralization-performance-tradeoff)
- Relacionado: [Ejecución paralela multi-motor de Bitroot: planificación, particionado y la superficie de conflictos](/es/blog/bitroot-evm)
- Siguiente: [Por qué una EVM de un solo hilo limita el TPS: historia de la congestión y el modelo de ejecución](/es/blog/evm-single-thread-bottleneck)
