肖臻《区块链技术与应用》第16讲 - 以太坊的“世界状态”:从哈希表到MPT架构演进
摘要:
以太坊采用的账户模型虽然直观,却带来了一个巨大的技术挑战:如何高效地存储并加密地保证全网数百万账户与各自状态之间的映射关系?本文基于北京大学肖臻老师的公开课内容,以一种追根溯源的方式,探讨了以太坊核心数据结构的设计演进。文章将带领读者经历一场逻辑推演,从分析为何哈希表、默克尔树等简单方案无法满足需求,到最终揭示以太坊的精妙解决方案——默克尔帕特里夏树(Merkle Patricia Trie)。我们将理解这一复杂结构如何集各家之长,实现了高效更新、状态证明和历史版本回溯等关键功能。
1. 问题的提出:如何存储全网账户?
以太坊是一个基于账户的模型,其核心任务是维护一个“世界状态”(World State),即一个从唯一的账户地址(Address)到该账户状态(Account State,包含余额、Nonce等)的映射。
从计算机科学的角度看,这是一个典型的键值对(Key-Value Pair)存储问题。那么,应该采用什么样的数据结构来实现这个映射呢?
2. 失败的探索:为何简单的方案行不通?
让我们跟随肖臻老师的思路,从最直观的方案开始探索,并分析它们各自的致命缺陷。
2.1 方案一:哈希表——高效但无法证明
- 优点: 哈希表在插入、查询和更新操作上具有极高的效率(平均时间复杂度为O(1))。
- 致命缺陷:无法高效地生成默克尔证明(Merkle Proof)。
- 为了让轻节点能够验证某个账户的状态(如余额),全节点必须提供一个加密的证明。这就需要将所有账户数据组织成一棵默克尔树,并计算出一个唯一的根哈希(Root Hash)存入区块头。
- 如果使用哈希表,意味着每产生一个新区块(约15秒),全节点都必须遍历全部数百万个账户,重新构建一棵庞大的默克尔树。这个计算开销是完全无法接受的。
- 对比比特币: 比特币的默克尔树只包含单个区块内的几百到几千笔交易,且每次都是构建一棵全新的、不可变的树,因此计算开销可控。而以太坊需要对一个庞大的、可变的“全网账户状态”进行操作。
2.2 方案二:默克尔树——可证明但笨拙
- 优点: 直接使用默克尔树,天然就解决了状态证明和全网状态一致性(通过对比根哈希)的问题。
- 致命缺陷:更新和查询效率低下,且根哈希不唯一。
- 效率问题: 传统的默克尔树并非为快速查找或更新单个叶子节点而设计。
- 根哈希不唯一: 如果不对叶子节点(账户)的顺序进行规定,那么不同的全节点在本地构建出的默克尔树结构将会不同,计算出的根哈希也不同,从而导致共识崩溃。
- 排序也没用: 如果我们强制要求所有节点按账户地址对叶子节点进行排序(Sorted Merkle Tree),虽然能保证根哈希唯一,但又会引入新的问题:当一个新账户(其地址是随机的)被创建时,它很可能需要插入到树的中间位置,这将导致树的大规模重构,其开销同样巨大。
3. 最终的答案:默克尔帕特里夏树
既然简单的方案都行不通,以太坊最终采用了一种更为复杂和精妙的数据结构:默克尔帕特里夏树(Merkle Patricia Trie, MPT)。它融合了多种数据结构的优点。
3.1 从Trie(前缀树)开始
Trie,又称字典树或前缀树,是一种键值存储结构,其路径由键的字符决定。对于以太坊的16进制地址,它有以下优点:
- 路径唯一: 任何一个地址都对应树中一条唯一的路径。
- 顺序无关: 插入键的顺序不影响最终生成的树的结构,保证了不同节点构建的树是相同的。
- 更新局部性好: 更新一个账户的状态,只会影响到该账户对应的那一条路径上的节点,无需改动整棵树。
3.2 优化:路径压缩与Patricia树
Trie树的一个缺点是,当路径上出现长串的“单传”节点时,会浪费存储空间。Patricia树(又称压缩前缀树)通过将这些单传路径压缩到一个节点内,极大地提高了效率。
这个优化对于以太坊至关重要,因为其地址空间(2¹⁶⁰)相对于实际存在的账户数量来说是极其稀疏的。路径压缩能显著降低树的高度和存储开销。
3.3 融合:MPT如何集各家之长
默克尔帕特里夏树(MPT)就是将默克尔树的加密特性与帕特里夏树的高效结构相结合的产物:
MPT = (Patricia Trie) + (Hash Pointers)
在MPT中,所有节点间的引用不再是普通的内存指针,而是哈希指针——即子节点内容的哈希值。这样,整棵树的所有状态信息就可以被加密地、唯一地汇总到一个根哈希中。这个根哈希就是被记录在以太坊区块头里的状态根(State Root)。
MPT完美地解决了之前所有的问题:它既能高效地进行增删查改,又能保证根哈希的唯一性,还能提供轻量级的默克尔证明。
4. MPT的实践:历史状态与区块回滚
4.1 写时复制:高效更新与状态共享
在以太坊中,当一个新区块产生导致状态变化时,MPT并不是在原地修改。它采用了一种“写时复制”(Copy-on-Write)的策略:只创建新的、已更改的节点,并让这些新节点指回到旧树中未发生变化的部分。
因此,每个区块都对应一个完整的状态树快照,但相邻区块的状态树之间共享了绝大部分未改变的节点,极大地节省了存储空间。
4.2 为何要保留历史?—— 为“回滚”而生
保留完整的历史状态树至关重要,其核心原因是为了支持区块回滚(Revert)。
- 以太坊十几秒的出块时间导致临时性分叉成为常态。当一个节点发现自己所在的分叉链被废弃时,它必须能将其“世界状态”安全、准确地回滚到分叉前的某个区块高度。
- 由于智能合约的逻辑可以是图灵完备的、极其复杂的,我们无法通过“反向操作”来推算出交易前的状态。
- 因此,必须保存完整的历史状态快照,使得回滚操作简化为一次简单的指针切换——只需将当前指向的状态根,切换回历史某个区块的状态根即可。
总结: MPT结构虽然复杂,但它是以太坊为了支撑其核心功能——图灵完备的智能合约——而做出的必然选择。它不仅解决了海量账户状态的存储和验证问题,更通过巧妙的设计,为处理频繁分叉和复杂的状态变更提供了坚实、可靠的基础。