以太坊状态树知识点
ETH 状态树:知识点总览

核心一句话:以太坊状态树是全局账户状态的加密承诺。它用 Modified Merkle Patricia Trie 维护
address -> account state,每个区块执行完交易后都会得到新的stateRoot,并写入区块头。
这一篇整理以太坊状态树的核心知识点:状态树是什么、为什么不用哈希表、为什么不用普通 Merkle Tree,以及状态树和合约存储之间的关系。
1. 先背下来的核心结论
以太坊采用账户模型,需要维护一份全局状态:
1 | address -> account state |
每个账户状态包含四个字段:
1 | nonce |
nonce 记录交易次数或合约创建次数。balance 是账户的 ETH 原生余额。storageRoot 指向合约账户自己的 Storage Trie。codeHash 是合约代码哈希。
状态树的根哈希叫 stateRoot。每个区块执行完成后,新的 stateRoot 会写入区块头,用来承诺当前完整世界状态。
2. 核心考点
状态树这一题通常考五层能力。
第一层:知道以太坊是账户模型,不是 UTXO 模型。
第二层:知道账户状态四元组:nonce、balance、storageRoot、codeHash。
第三层:知道状态树不是普通数据库,而是带根哈希和证明能力的 MPT。
第四层:能解释为什么普通哈希表、普通 Merkle Tree、Sorted Merkle Tree 都不够合适。
第五层:能把状态树、合约 Storage Trie、ERC-20 余额、分叉回滚、Merkle Proof 串起来。
3. 为什么以太坊需要状态树
比特币主要维护 UTXO Set,关注“哪些输出还没被花费”。以太坊采用账户模型,关注“每个账户现在处于什么状态”。
以太坊中的账户分为两类:
1 | EOA:外部账户,由私钥控制 |
普通账户主要关心 ETH 余额和 nonce。合约账户除了余额和 nonce,还需要保存合约代码以及合约内部变量。
因此,以太坊需要维护一份巨大的动态键值映射:
1 | keccak256(address) -> RLP(account state) |
这份映射不是只给本地节点查数据库用。它还必须让所有节点对同一份状态得到同一个根哈希,并支持轻节点验证某个账户状态是否真实存在。
4. 为什么不能只用哈希表
如果只看查询速度,哈希表很自然:
1 | map[address] = accountState |
普通哈希表平均查询复杂度接近 O(1),但它缺少区块链需要的状态承诺能力。
第一个问题是没有统一根哈希。区块链需要用一个短值代表整份全局状态,所有节点通过这个值判断状态是否一致。
第二个问题是不支持 Merkle Proof。轻节点不能只相信全节点口头返回的余额,它需要一条可验证的证明路径。
第三个问题是难以证明不存在。判断某个账户不存在,不能靠“数据库没查到”这种本地结论,而要能绑定到某个区块头的 stateRoot。
所以哈希表适合本地查找,但不能直接作为区块链共识层的状态承诺结构。
5. 为什么不能直接用普通 Merkle Tree
普通 Merkle Tree 能提供根哈希,也能证明某个叶子属于这棵树。比特币交易树就是典型例子。
但以太坊状态不是固定列表,而是动态键值映射:
1 | address -> account state |
普通 Merkle Tree 不擅长按 key 查找。它只知道叶子位置,不知道某个地址应该走到哪个叶子。
普通 Merkle Tree 还依赖叶子顺序。不同节点如果以不同顺序组织账户,即使账户内容一样,也会得到不同 root。
Sorted Merkle Tree 可以按 key 排序,但新增账户可能插入到中间位置,导致后续叶子位置和父节点结构大范围变化。
以太坊需要的是“key 自己决定路径”的结构,所以 Trie 比普通 Merkle Tree 更适合。
6. Trie、Patricia Trie 和 MPT
Trie 也叫前缀树。它用 key 的每一位决定路径:
1 | root -> a -> 7 -> f -> 3 -> ... |
这个特性适合键值映射,因为查找路径由 key 决定,不需要额外排序。
普通 Trie 的问题是路径可能很长。如果只有一个 key abcdef,普通 Trie 可能展开成六层单分支节点。
Patricia Trie 会压缩没有分叉的路径:
1 | root -> abcdef -> value |
Merkle Patricia Trie 可以理解为:
1 | Patricia Trie + Merkle Hash |
Patricia Trie 负责高效查找和路径压缩。Merkle Hash 负责防篡改、根哈希承诺和 Merkle Proof。
7. 以太坊的 Modified MPT
以太坊实际使用的是 Modified Merkle Patricia Trie。Modified 主要体现为节点编码、路径编码、节点引用和存储优化。
MPT 中常见三类节点:
1 | Branch Node |
Branch Node 最多有 16 个子方向,因为以太坊把 key 拆成十六进制 nibble,每一位是 0 到 f。
Extension Node 用于压缩公共前缀。多个 key 共享一段路径时,这段路径可以用一个扩展节点表示。
Leaf Node 保存最终 value。对状态树来说,value 通常是 RLP 编码后的账户状态。
8. 为什么 key 通常是地址哈希
账户地址本身是 160 bit,也就是 20 字节。以太坊状态树通常使用地址的 Keccak-256 哈希作为路径 key:
1 | key = keccak256(address) |
这样做可以让 key 分布更均匀,避免攻击者构造大量相似前缀地址,让 Trie 结构退化。
哈希后的路径空间接近 256 bit,极度稀疏。因此路径压缩很重要,否则普通 Trie 会浪费大量中间节点。
9. RLP 编码的作用
账户状态在进入 MPT 前需要被序列化。以太坊使用 RLP,即 Recursive Length Prefix。
账户状态会被组织成列表:
1 | [nonce, balance, storageRoot, codeHash] |
然后编码为确定的字节序列:
1 | RLP([nonce, balance, storageRoot, codeHash]) |
哈希函数处理的是字节序列。如果不同节点对同一个账户状态使用不同编码,最终会得到不同哈希和不同 stateRoot。
RLP 的核心价值是确定性。所有节点必须对同一份状态得到完全相同的字节表示。
10. 状态树和合约 Storage Trie 的关系
状态树维护的是账户级别状态。合约自己的变量不会直接摊开放在全局状态树里,而是由合约账户的 storageRoot 指向一棵 Storage Trie。
可以把结构理解为:
1 | Block Header |
这个点很常见,因为它能区分 ETH 余额和 ERC-20 余额。
Alice 有 3 ETH,存储在 Alice 账户状态的 balance 字段。
Alice 有 100 USDT,通常存储在 USDT 合约 Storage Trie 中的 balances[Alice]。
所以 ERC-20 余额不是 Alice 账户的 balance。Alice 账户的 balance 只表示 ETH 原生资产。
11. 状态变化如何影响 stateRoot
假设 Alice 给 Bob 转 1 ETH。执行前:
1 | Alice.balance = 5 ETH |
执行后:
1 | Alice.balance = 4 ETH - gas |
Alice 和 Bob 的账户状态改变,对应叶子节点 value 改变。叶子哈希改变后,路径上的父节点哈希逐层改变,最终 stateRoot 改变。
没有变化的账户不需要重新构建。新旧状态树可以共享大量未变节点,这类似 Git 对未变化对象的复用。
12. 为什么不能原地修改状态树
区块链会出现临时分叉:
1 | Block 100 |
节点可能先执行 101B,后来发现 101A 才是规范链。如果状态原地覆盖,旧状态很难恢复。
智能合约执行也不容易反推。一次 DeFi 交易可能修改多个 storage slot、跨合约调用、更新协议统计变量。
所以以太坊需要能引用旧状态版本。每个区块头中的 stateRoot 都代表该高度执行完成后的世界状态版本。
13. Merkle Proof 和不存在性证明
轻节点不保存完整状态树,但可以保存区块头。只要知道区块头中的 stateRoot,就能验证账户状态证明。
证明流程可以简化为:
1 | 区块头 stateRoot |
验证者重新计算路径上节点哈希。如果最终根哈希等于区块头里的 stateRoot,就能确认该账户状态属于这个区块的世界状态。
MPT 也能证明不存在。因为路径由 key 决定,如果路径中某个分支缺失,或者最终叶子路径不匹配,就能证明该 key 不在树中。
14. 为什么状态树必须是全局的
一个常见误区是:每个区块只保存本区块涉及账户的状态,是否可以节省空间?
这个方案不可行。假设 Alice 很久没交易,查询 Alice 当前余额时,就必须从最新区块一直向前找,直到找到 Alice 最后一次出现的位置。
判断账户不存在会更麻烦。当前区块没有 Alice,可能是 Alice 不存在,也可能只是这个区块没用到 Alice。
以太坊的设计是:
1 | 每个区块的 stateRoot 都承诺该区块执行后的完整全局状态 |
这不代表每个区块完整复制一棵树。MPT 是持久化结构,新旧版本共享未变化节点,只更新变化路径。
15. 和交易树、收据树的关系
以太坊区块头里常见三类根:
1 | stateRoot |
stateRoot 承诺区块执行完成后的全局状态。它是跨区块持续演进的状态版本。
transactionsRoot 承诺当前区块包含哪些交易,以及交易顺序是什么。
receiptsRoot 承诺当前区块中每笔交易执行后的结果、gas 使用和事件日志。
状态树是理解以太坊数据结构的主线,交易树和收据树是它的对照组。下一篇会专门整理交易树和收据树。
16. 高频问答
Q1:以太坊状态树保存什么?
保存全局账户状态映射。逻辑上是 address -> account state,工程上通常是 keccak256(address) -> RLP(account state)。
Q2:账户状态四个字段是什么?
nonce、balance、storageRoot、codeHash。
Q3:ERC-20 余额存在用户账户里吗?
不在。用户账户的 balance 是 ETH 原生余额。ERC-20 余额存在代币合约自己的 Storage Trie 中。
Q4:为什么不用普通哈希表?
哈希表查找快,但没有全局根哈希,也不能直接提供 Merkle Proof 和不存在性证明。
Q5:为什么不用普通 Merkle Tree?
普通 Merkle Tree 更适合固定有序列表,不适合动态键值映射。它还需要额外解决 key 查找和叶子顺序一致性问题。
Q6:为什么状态树更新不是全量重建?
MPT 可以共享未变化节点。交易只影响相关账户或 storage slot,因此只需要更新变化路径上的节点。
Q7:状态树如何支持轻节点?
轻节点保存区块头和 stateRoot,向全节点请求 Merkle Proof,然后验证账户状态是否能回算到该根哈希。
Q8:为什么需要历史状态版本?
临时分叉、回滚和链重组需要旧状态。原地修改会让节点难以从错误分支切回规范链。
17. 易错点
不要把 balance 和 ERC-20 余额混为一谈。balance 是 ETH,ERC-20 余额是合约存储。
不要说每个区块完整复制一棵状态树。逻辑上每个区块有一个状态版本,工程上大量节点共享。
不要把状态树、交易树、收据树说成同一类业务含义。它们都可以用 trie,但承诺对象不同。
不要忽略 RLP。哈希前必须有确定的字节编码,否则不同节点无法得到一致根哈希。
18. 总结
以太坊状态树的本质是全局世界状态的加密承诺。它把账户模型、MPT、RLP、Merkle Proof、合约存储和区块头 stateRoot 连接在一起。
总结时,不要只说“以太坊用 MPT”。更完整的说法是:以太坊需要维护动态全局账户状态,普通哈希表缺少证明能力,普通 Merkle Tree 不适合动态键值映射,所以使用 Modified MPT,在每个区块执行后得到新的 stateRoot。
- 标题: 以太坊状态树知识点
- 作者: Kylinxin
- 创建于 : 2026-06-04 09:16:00
- 更新于 : 2026-06-22 10:00:00
- 链接: https://kylinxin.github.io/2026/06/04/以太坊状态树知识点/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。