摘要:
我们已经了解,以太坊通过精妙的默克尔帕特里夏树来维护全网的“世界状态”。但除此之外,为了构建一个功能完备的去中心化应用平台,每个以太坊区块还包含了另外两棵同样重要的数据结构:交易树和收据树。本文基于北京大学肖臻老师的公开课内容,深入探讨了这“三位一体”的树状结构,并重点剖析了以太坊为实现高效历史事件查询而引入的布隆过滤器(Bloom Filter)机制。最后,文章将以太坊的整个运作模式抽象为一台“交易驱动的状态机”,帮助读者从更高维度理解其运行逻辑。
1. 以太坊的三棵树:状态、交易与收据
与比特币的区块头只包含一个默克尔根(交易树根)不同,以太坊的区块头中包含了三个树的根哈希,分别对应状态树、交易树和收据树。
1.1 树的重温与对比
- 状态树 (State Trie):这是我们上节课的主角。它是全局性的,记录了所有账户的当前状态。为了节省空间,相邻区块的状态树会共享大量未发生变化的节点。
- 交易树 (Transaction Trie):这棵树是区块局部的,仅包含当前区块内的所有交易。它的键(Key)是交易在区块中的序号(0, 1, 2…)。
- 收据树 (Receipts Trie):这棵树同样是区块局部的,与交易树一一对应。每个交易执行完毕后,都会生成一个“收据”(Receipt),记录其执行结果(如消耗的Gas、生成的日志事件等)。这棵树就包含了当前区块所有交易的收据。
一个重要区别: 与状态树不同,每个新区块的交易树和收据树都是全新创建的,它们之间不会共享任何节点。
1.2 为何都用MPT?—— 架构的统一性
比特币的交易树是一棵普通的默克尔树。以太坊的三棵树则统一采用了更复杂的默克尔帕特里夏树(MPT)结构。肖臻老师推测,这并非有深奥的技术原因,而很可能是为了代码的统一和便于管理,让三种不同的数据都能复用同一套数据结构实现。
2. 轻节点的困境:如何在海量历史中高效检索?
以太坊的设计目标之一是支持轻节点(Light Client)。轻节点只下载区块头,不下载完整的交易数据。这就带来了一个挑战:一个轻节点如何查询历史事件?
例如,一个去中心化应用(dApp)的前端需要查询“过去十天中,所有与某个特定智能合约地址相关的交易事件”。
- 笨办法: 扫描过去十天的所有区块,检查其中的每一笔交易。这对于轻节点来说是不可能的,因为它根本没有这些数据。
- 聪明的办法: 以太坊引入了布隆过滤器(Bloom Filter)机制,为轻节点提供了一种高效的“筛查”手段。
3. 概率的威力:布隆过滤器入门
布隆过滤器是一种空间效率极高的概率型数据结构,它用于快速判断一个元素是否可能存在于一个集合中。
3.1 原理:快速判断“一定不在”
- 结构: 一个很长的位向量(bit vector),初始全为0。
- 添加元素: 当要向集合中添加一个元素(如一个合约地址)时,会用多个不同的哈希函数对该元素进行计算,得到多个位置索引。然后,将位向量在这些索引位置上的值设为1。
- 查询元素: 当要查询一个元素是否存在时,同样用那几个哈希函数计算出对应的索引位置。
- 如果这些位置中有任何一个的值是0,那么该元素一定不在集合中(无漏报,No False Negatives)。
- 如果这些位置全部都是1,那么该元素可能在集合中(有误报,Possible False Positives)。“误报”是因为其他元素的哈希结果也可能恰好将这些位置设为了1。
3.2 局限性:不支持删除
标准的布隆过滤器不支持删除元素。因为将某个位置的1改回0,可能会影响到其他同样映射到该位置的元素,破坏数据结构的正确性。
4. 布隆过滤器在以太坊中的应用
以太坊巧妙地将布隆过滤器嵌入到了收据和区块头中。
4.1 从“交易收据”到“区块头”的层层汇总
- 每个交易收据都包含一个小的布隆过滤器: 这个过滤器由该交易执行时产生的所有日志(Logs/Events)信息(如合约地址、事件主题等)生成。
- 每个区块头都包含一个大的布隆过滤器: 这个大的过滤器是该区块内所有交易收据的小布隆过滤器的并集(通过按位或运算
OR
合并)。它相当于整个区块所有事件的紧凑摘要。
4.2 高效的“筛查”流程
现在,轻节点查询历史事件的流程变得非常高效:
- 轻节点想查找与某个合约地址
A
相关的交易。 - 它只需遍历它已下载的区块头列表,检查每个区块头的大布隆过滤器。
- 如果区块头的布隆过滤器显示地址
A
一定不在,那么这个区块就可以被安全地、快速地跳过。 - 只有当布隆过滤器显示地址
A
可能存在时,轻节点才需要向全节点请求该区块的详细收据信息,做进一步的精确确认,以排除可能的“误报”。
通过这种方式,轻节点能够以极低的成本过滤掉绝大多数不相关的区块,极大地提高了历史事件的查询效率。
5. 终极视角:交易驱动的状态机
从一个更高的维度看,以太坊和比特币都可以被理解为一台交易驱动的状态机(Transaction-Driven State Machine)。
- 状态:
- 以太坊的状态是“世界状态”,即所有账户的状态集合,由状态树完整地表示。
- 比特币的状态是所有未花费的交易输出集合,即UTXO集。
- 状态转移:
- 每一个新的区块,都包含一组交易。
- 这些交易的执行,会驱动整个系统从当前状态(State N),确定性地转移到下一个状态(State N+1)。
一个关键问题: 为什么以太坊的状态树必须包含所有账户的状态,而不能只包含当前区块发生变化的那些账户?
答案: 为了保证高效的状态查询。如果状态树只记录增量变化,那么当需要查询一个长期未活动账户的余额时,就必须从当前区块一路回溯扫描,直到找到该账户最后一次出现的位置,这在实践中是不可行的。而维护一个完整的状态快照,可以保证对任何账户的查询都能高效完成。